@vue-skuilder/standalone-ui 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{TagViewer-nh28Samo.js → TagViewer-B9lUMa1N.js} +2 -2
- package/dist/assets/{TagViewer-nh28Samo.js.map → TagViewer-B9lUMa1N.js.map} +1 -1
- package/dist/assets/{common-ui.es-CKDaicTB.js → common-ui.es-CS663iWK.js} +5 -5
- package/dist/assets/common-ui.es-CS663iWK.js.map +1 -0
- package/dist/assets/common-ui.es-D92yaKLw.js +1 -0
- package/dist/assets/{dist-mJg7wGOe.js → dist-DHtsWAkb.js} +8 -8
- package/dist/assets/{dist-mJg7wGOe.js.map → dist-DHtsWAkb.js.map} +1 -1
- package/dist/assets/{index-BTx-tECR.js → index-Cib2NGQ_.js} +5 -5
- package/dist/assets/{index-BTx-tECR.js.map → index-Cib2NGQ_.js.map} +1 -1
- package/dist/assets/{index-BjQg_GYs.css → index-yhC6an4B.css} +1 -1
- package/dist/index.html +4 -4
- package/dist-lib/questions.cjs.js +8 -8
- package/dist-lib/questions.cjs.js.map +1 -1
- package/dist-lib/questions.mjs +44 -28
- package/dist-lib/questions.mjs.map +1 -1
- package/package.json +6 -6
- package/dist/assets/common-ui.es-CKDaicTB.js.map +0 -1
- package/dist/assets/common-ui.es-CcyGjPTk.js +0 -1
package/dist/index.html
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"
|
|
10
10
|
rel="stylesheet"
|
|
11
11
|
/>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-Cib2NGQ_.js"></script>
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/dist-DCANvFNh.js">
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-DHtsWAkb.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/MarkdownRenderer-kStoDRNE-lsnQ9xYB.js">
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/common-ui.es-
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/common-ui.es-CS663iWK.js">
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/common-ui-BY2Ut5jf.css">
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
18
|
+
<link rel="stylesheet" crossorigin href="/assets/index-yhC6an4B.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
|
21
21
|
<div id="app"></div>
|
|
@@ -395,17 +395,17 @@ Examples:
|
|
|
395
395
|
window.skuilder.userdb.showScheduledReviews('course123')
|
|
396
396
|
window.skuilder.userdb.queryByType('SCHEDULED_CARD', 10)
|
|
397
397
|
window.skuilder.userdb.raw(db => db.allDocs({ limit: 5 }))
|
|
398
|
-
`)}},mountUserDBDebugger()}}),init_core=__esm({"src/core/index.ts"(){"use strict";init_interfaces(),init_types_legacy(),init_user(),init_strategyState(),init_userOutcome(),init_Loggable(),init_util(),init_navigators(),init_bulkImport(),init_orchestration(),init_UserDBDebugger()}}),init_core(),init_courseLookupDB(),init_logger(),init_couch(),init_util(),init_logger(),duration=hooks.duration,init_logger(),SrsService=class{constructor(t){_defineProperty$2(this,`user`,void 0),this.user=t}removeReview(t){logger.info(`[SrsService] Removing orphaned scheduled review: ${t}`),this.user.removeScheduledCardReview(t)}async scheduleReview(t,c){let u=newInterval(this.user,t),d=hooks.utc().add(u,`seconds`);isReview(c)&&(logger.info(`[SrsService] Removing previously scheduled review for: ${c.cardID}`),this.user.removeScheduledCardReview(c.reviewID)),this.user.scheduleCardReview({user:this.user.getUsername(),course_id:t.courseID,card_id:t.cardID,time:d,scheduledFor:c.contentSourceType,schedulingAgentId:c.contentSourceID})}},init_logger(),EloService=class{constructor(t,c){_defineProperty$2(this,`dataLayer`,void 0),_defineProperty$2(this,`user`,void 0),this.dataLayer=t,this.user=c}async updateUserAndCardElo(t,c,u,d,m,g){g&&logger.warn(`k value interpretation not currently implemented`);let b=this.dataLayer.getCourseDB(m.card.course_id),S=
|
|
399
|
-
User: ${JSON.stringify(
|
|
400
|
-
Card: ${JSON.stringify(
|
|
398
|
+
`)}},mountUserDBDebugger()}}),init_core=__esm({"src/core/index.ts"(){"use strict";init_interfaces(),init_types_legacy(),init_user(),init_strategyState(),init_userOutcome(),init_Loggable(),init_util(),init_navigators(),init_bulkImport(),init_orchestration(),init_UserDBDebugger()}}),init_core(),init_courseLookupDB(),init_logger(),init_couch(),init_util(),init_logger(),duration=hooks.duration,init_logger(),SrsService=class{constructor(t){_defineProperty$2(this,`user`,void 0),this.user=t}removeReview(t){logger.info(`[SrsService] Removing orphaned scheduled review: ${t}`),this.user.removeScheduledCardReview(t)}async scheduleReview(t,c){let u=newInterval(this.user,t),d=hooks.utc().add(u,`seconds`);isReview(c)&&(logger.info(`[SrsService] Removing previously scheduled review for: ${c.cardID}`),this.user.removeScheduledCardReview(c.reviewID)),this.user.scheduleCardReview({user:this.user.getUsername(),course_id:t.courseID,card_id:t.cardID,time:d,scheduledFor:c.contentSourceType,schedulingAgentId:c.contentSourceID})}},init_logger(),EloService=class{constructor(t,c){_defineProperty$2(this,`dataLayer`,void 0),_defineProperty$2(this,`user`,void 0),this.dataLayer=t,this.user=c}async updateUserAndCardElo(t,c,u,d,m,g){g&&logger.warn(`k value interpretation not currently implemented`);let b=this.dataLayer.getCourseDB(m.card.course_id),S=d.courses.find(t=>t.courseID===c);if(!S){logger.error(`[EloService] No registration for course ${c} on user's registration doc \u2014 skipping ELO update for card ${u}. (Is the user registered for this course?)`);return}let C=toCourseElo(S.elo),w=(await b.getCardEloData([m.card.card_id]))[0];if(w&&C){let d=adjustCourseScores(C,w,t);S.elo=d.userElo;let m=await Promise.allSettled([this.user.updateUserElo(c,d.userElo),b.updateCardElo(u,d.cardElo)]),g=m[0].status===`fulfilled`,T=m[1].status===`fulfilled`;if(g&&T){let t=m[0].value,c=m[1].value;t.ok&&c&&c.ok&&logger.info(`[EloService] Updated ELOS:
|
|
399
|
+
User: ${JSON.stringify(d.userElo)})
|
|
400
|
+
Card: ${JSON.stringify(d.cardElo)})
|
|
401
401
|
`)}else logger.warn(`[EloService] Partial ELO update:
|
|
402
|
-
User ELO update: ${
|
|
403
|
-
Card ELO update: ${T?`SUCCESS`:`FAILED`}`),!
|
|
402
|
+
User ELO update: ${g?`SUCCESS`:`FAILED`}
|
|
403
|
+
Card ELO update: ${T?`SUCCESS`:`FAILED`}`),!g&&m[0].status===`rejected`&&logger.error(`[EloService] User ELO update error:`,m[0].reason),!T&&m[1].status===`rejected`&&logger.error(`[EloService] Card ELO update error:`,m[1].reason)}}async updateUserAndCardEloPerTag(t,c,u,d,m){let g=this.dataLayer.getCourseDB(m.card.course_id),b=d.courses.find(t=>t.courseID===c);if(!b){logger.error(`[EloService] No registration for course ${c} on user's registration doc \u2014 skipping per-tag ELO update for card ${u}. (Is the user registered for this course?)`);return}let S=toCourseElo(b.elo),[C,w]=await Promise.all([g.getCardEloData([m.card.card_id]),g.getAppliedTagsBatch([u])]),T=C[0],E=w.get(u)??[],D={...t},O=t._global;for(let t of E)t in D||(D[t]=O);if(T&&S){let t=adjustCourseScoresPerTag(S,T,D);b.elo=t.userElo;let d=await Promise.allSettled([this.user.updateUserElo(c,t.userElo),g.updateCardElo(u,t.cardElo)]),m=d[0].status===`fulfilled`,C=d[1].status===`fulfilled`;if(m&&C){let c=d[0].value,u=d[1].value;if(c.ok&&u&&u.ok){let c=Object.keys(D).length-1;logger.info(`[EloService] Updated ELOS (per-tag, ${c} tags):
|
|
404
404
|
User: ${JSON.stringify(t.userElo)})
|
|
405
405
|
Card: ${JSON.stringify(t.cardElo)})
|
|
406
406
|
`)}}else logger.warn(`[EloService] Partial ELO update (per-tag):
|
|
407
|
-
User ELO update: ${
|
|
408
|
-
Card ELO update: ${C?`SUCCESS`:`FAILED`}`),!
|
|
407
|
+
User ELO update: ${m?`SUCCESS`:`FAILED`}
|
|
408
|
+
Card ELO update: ${C?`SUCCESS`:`FAILED`}`),!m&&d[0].status===`rejected`&&logger.error(`[EloService] User ELO update error:`,d[0].reason),!C&&d[1].status===`rejected`&&logger.error(`[EloService] Card ELO update error:`,d[1].reason)}}},init_core(),init_logger(),ResponseProcessor=class{constructor(t,c){_defineProperty$2(this,`srsService`,void 0),_defineProperty$2(this,`eloService`,void 0),this.srsService=t,this.eloService=c}logEloFailure(t,c){return u=>logger.error(`[ResponseProcessor] ELO update failed (${t}) for ${c}:`,u)}parsePerformance(t){return typeof t==`number`?{globalScore:t,taggedPerformance:null}:isTaggedPerformance(t)?{globalScore:t._global,taggedPerformance:t}:(logger.warn(`[ResponseProcessor] Unexpected performance structure, using neutral score`,{performance:t}),{globalScore:.5,taggedPerformance:null})}async processResponse(t,c,u,d,m,g,b,S,C,w){if(!isQuestionRecord(t))return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,shouldClearFeedbackShadow:!0};try{let T=await c,E;return E=t.isCorrect?this.processCorrectResponse(t,T,u,d,m,g,b):this.processIncorrectResponse(t,T,d,m,g,b,S,C,w),t.deferAdvance&&E.shouldLoadNextCard&&(logger.info(`[ResponseProcessor] deferAdvance requested — suppressing navigation, action stashed:`,{nextCardAction:E.nextCardAction}),E={...E,shouldLoadNextCard:!1,deferred:!0}),E}catch(t){throw logger.error(`[ResponseProcessor] Failed to load card history`,{e:t,cardId:b}),t}}processCorrectResponse(t,c,u,d,m,g,b){if(t.priorAttemps===0){m.card.tags.includes(`srs:skip`)||this.srsService.scheduleReview(c,u);let{globalScore:S,taggedPerformance:C}=this.parsePerformance(t.performance);if(C){let u=Object.keys(C).filter(t=>t!==`_global`),S=u.filter(t=>C[t]===null),w=u.filter(t=>C[t]!==null);logger.info(`[FirstContactElo] correct first-attempt per-tag ELO update for ${b} (historyLen=${c.records.length}, priorAttemps=${t.priorAttemps}): scored=[${w.join(`, `)}] count-only=[${S.join(`, `)}]`),this.eloService.updateUserAndCardEloPerTag(C,g,b,d,m).catch(this.logEloFailure(`correct per-tag`,b))}else{let u=.5+S/2;if(c.records.length===1)this.eloService.updateUserAndCardElo(u,g,b,d,m).catch(this.logEloFailure(`correct`,b));else{let t=Math.ceil(32/c.records.length);this.eloService.updateUserAndCardElo(u,g,b,d,m,t).catch(this.logEloFailure(`correct repeat-view`,b))}logger.info(`[FirstContactElo] correct first-attempt ELO update (score=${u.toFixed(3)}) for ${b} (historyLen=${c.records.length}, priorAttemps=${t.priorAttemps})`)}return{nextCardAction:`dismiss-success`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:S,shouldClearFeedbackShadow:!0}}else{logger.info(`[ResponseProcessor] Processed correct response (retry attempt - no scheduling/ELO)`);let{globalScore:c}=this.parsePerformance(t.performance);return{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!0,performanceScore:c,shouldClearFeedbackShadow:!0}}}processIncorrectResponse(t,c,u,d,m,g,b,S,C){let{taggedPerformance:w}=this.parsePerformance(t.performance),T=!1;return t.priorAttemps===0?(w?(this.eloService.updateUserAndCardEloPerTag(w,m,g,u,d).catch(this.logEloFailure(`incorrect per-tag`,g)),logger.info(`[FirstContactElo] incorrect first-attempt per-tag ELO update for ${g} (historyLen=${c.records.length}, priorAttemps=${t.priorAttemps}, tags=${Object.keys(w).length-1})`)):(this.eloService.updateUserAndCardElo(0,m,g,u,d).catch(this.logEloFailure(`incorrect`,g)),logger.info(`[FirstContactElo] incorrect first-attempt ELO update (score=0) for ${g} (historyLen=${c.records.length}, priorAttemps=${t.priorAttemps})`)),T=!0):logger.info(`[FirstContactElo] incorrect retry \u2014 no ELO update for ${g} (historyLen=${c.records.length}, priorAttemps=${t.priorAttemps})`),d.records.length>=b?C>=S?(T?logger.info(`[FirstContactElo] dismiss-failed \u2014 ELO already updated this response, skipping double penalty for ${g}`):(w?this.eloService.updateUserAndCardEloPerTag(w,m,g,u,d).catch(this.logEloFailure(`dismiss-failed per-tag`,g)):this.eloService.updateUserAndCardElo(0,m,g,u,d).catch(this.logEloFailure(`dismiss-failed`,g)),logger.info(`[FirstContactElo] dismiss-failed final ELO penalty for ${g} (historyLen=${c.records.length}, sessionViews=${C})`)),{nextCardAction:`dismiss-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}):{nextCardAction:`marked-failed`,shouldLoadNextCard:!0,isCorrect:!1,shouldClearFeedbackShadow:!0}:{nextCardAction:`none`,shouldLoadNextCard:!1,isCorrect:!1,shouldClearFeedbackShadow:!0}}},init_logger(),CardHydrationService=class{constructor(t,c,u){_defineProperty$2(this,`hydratedCards`,new Map),_defineProperty$2(this,`hydrationInFlight`,new Set),_defineProperty$2(this,`hydrationInProgress`,!1),this.getViewComponent=t,this.getCourseDB=c,this.getItemsToHydrate=u}getHydratedCard(t){return this.hydratedCards.get(t)??null}hasHydratedCard(t){return this.hydratedCards.has(t)}removeCard(t){this.hydratedCards.delete(t)}async ensureHydratedCards(){this.fillHydratedCards()}async waitForCard(t){if(this.hydratedCards.has(t))return this.hydratedCards.get(t);this.hydrationInProgress||this.fillHydratedCards();let c=1e4,u=25,d=0;for(;d<1e4;){if(this.hydratedCards.has(t))return this.hydratedCards.get(t);if(!this.hydrationInFlight.has(t)&&!this.hydrationInProgress)break;await new Promise(t=>setTimeout(t,25)),d+=25}return this.hydratedCards.get(t)??null}get hydratedCount(){return this.hydratedCards.size}getHydratedCardIds(){return Array.from(this.hydratedCards.keys())}async fillHydratedCards(){if(!this.hydrationInProgress){this.hydrationInProgress=!0;try{let t=this.getItemsToHydrate();for(let c of t)if(!(this.hydratedCards.has(c.cardID)||this.hydrationInFlight.has(c.cardID)))try{await this.hydrateCard(c)}catch(t){logger.error(`[CardHydrationService] Error hydrating card ${c.cardID}:`,t)}}finally{this.hydrationInProgress=!1}}}async hydrateCard(t){if(!(this.hydratedCards.has(t.cardID)||this.hydrationInFlight.has(t.cardID))){this.hydrationInFlight.add(t.cardID);try{let c=this.getCourseDB(t.courseID),[u,d]=await Promise.all([c.getCourseDoc(t.cardID),c.getAppliedTagsBatch([t.cardID])]);isCourseElo(u.elo)||(u.elo=toCourseElo(u.elo));let m=this.getViewComponent(u.id_view),g=await Promise.all(u.id_displayable_data.map(t=>c.getCourseDoc(t,{attachments:!0,binary:!0}))),b=[];g.forEach(t=>{t.data.forEach(t=>{b.push(...parseAudioURIs(t.data))})});let S=[...new Set(b)];S.length>0&&(logger.debug(`[CardHydrationService] Prefetching ${S.length} audio files for card ${t.cardID}`),await Promise.allSettled(S.map(prefetchAudio)));let C=g.map(displayableDataToViewData).reverse();this.hydratedCards.set(t.cardID,{item:t,view:m,data:C,tags:d.get(t.cardID)??[]}),logger.debug(`[CardHydrationService] Hydrated card ${t.cardID}`)}finally{this.hydrationInFlight.delete(t.cardID)}}}},ItemQueue=class{constructor(){_defineProperty$2(this,`q`,[]),_defineProperty$2(this,`seenCardIds`,[]),_defineProperty$2(this,`_dequeueCount`,0)}get dequeueCount(){return this._dequeueCount}add(t,c){this.seenCardIds.find(t=>t===c)||(this.seenCardIds.push(c),this.q.push(t))}addAll(t,c){t.forEach(t=>this.add(t,c(t)))}get length(){return this.q.length}peek(t){return this.q[t]}dequeue(t){if(this.q.length!==0){this._dequeueCount++;let c=this.q.splice(0,1)[0];if(t){let u=t(c),d=this.seenCardIds.indexOf(u);d>-1&&this.seenCardIds.splice(d,1)}return c}else return null}replaceAll(t,c){this.q=[],this.seenCardIds=[];for(let u of t){let t=c(u);this.seenCardIds.includes(t)||(this.seenCardIds.push(t),this.q.push(u))}}mergeToFront(t,c,u){let d=0,m=[];for(let g of t){let t=c(g);if(!this.seenCardIds.includes(t))this.seenCardIds.push(t),m.push(g),d++;else if(u?.has(t)){let u=this.q.findIndex(u=>c(u)===t);u>=0&&m.push(...this.q.splice(u,1))}}return this.q.unshift(...m),d}get toString(){return`${typeof this.q[0]}:
|
|
409
409
|
`+this.q.map(t=>` ${t.courseID}+${t.cardID}: ${t.status}`).join(`
|
|
410
410
|
`)}},init_couch(),init_core(),init_recording(),init_Loggable(),init_types_legacy(),init_logger(),CouchDBToStaticPacker=class{constructor(t={}){_defineProperty$2(this,`config`,void 0),_defineProperty$2(this,`sourceDB`,null),this.config={chunkSize:1e3,includeAttachments:!0,...t}}async packCourse(t,c){logger.info(`Starting static pack for course: ${c}`),this.sourceDB=t;let u={version:`1.0.0`,courseId:c,courseName:``,courseConfig:null,lastUpdated:new Date().toISOString(),documentCount:0,chunks:[],indices:[],designDocs:[]},d=await this.extractCourseConfig(t);u.courseName=d.name,u.courseConfig=d,u.designDocs=await this.extractDesignDocs(t);let m=await this.extractDocumentsByType(t),g=new Map;this.config.includeAttachments&&await this.extractAllAttachments(m,g);let b=new Map;for(let[t,c]of Object.entries(m)){let d=this.createChunks(c,t);u.chunks.push(...d),u.documentCount+=c.length,this.prepareChunkData(d,c,b)}let S=new Map;return u.indices=await this.buildIndices(m,u.designDocs,S),{manifest:u,chunks:b,indices:S,attachments:g}}async packCourseToFiles(t,c,u,d){logger.info(`Packing course ${c} to files in ${u}`);let m=await this.packCourse(t,c),g=await this.writePackedDataToFiles(m,u,d);return{manifest:m.manifest,filesWritten:g,attachmentsFound:m.attachments?m.attachments.size:0}}async writePackedDataToFiles(t,c,u){let d=0;await u.ensureDir(c);let m=u.joinPath(c,`manifest.json`);await u.writeJson(m,t.manifest,{spaces:2}),d++,logger.info(`Wrote manifest: ${m}`);let g=u.joinPath(c,`chunks`),b=u.joinPath(c,`indices`);await u.ensureDir(g),await u.ensureDir(b);for(let[c,m]of t.chunks){let t=u.joinPath(g,`${c}.json`);await u.writeJson(t,m),d++}logger.info(`Wrote ${t.chunks.size} chunk files`);for(let[c,m]of t.indices){let t=u.joinPath(b,`${c}.json`);await u.writeJson(t,m,{spaces:2}),d++}if(logger.info(`Wrote ${t.indices.size} index files`),t.attachments&&t.attachments.size>0){for(let[m,g]of t.attachments){let t=u.joinPath(c,m),b=u.dirname(t);await u.ensureDir(b),await u.writeFile(t,g.buffer),d++}logger.info(`Wrote ${t.attachments.size} attachment files`)}return d}async extractCourseConfig(t){try{return await t.get(`CourseConfig`)}catch(t){throw logger.error(`Failed to extract course config:`,t),Error(`Course config not found`)}}async extractDesignDocs(t){return(await t.allDocs({startkey:`_design/`,endkey:`_design/`,include_docs:!0})).rows.map(t=>({_id:t.id,views:t.doc.views||{}}))}async extractDocumentsByType(t){let c=await t.allDocs({include_docs:!0}),u={};for(let t of c.rows){if(t.id.startsWith(`_`))continue;let c=t.doc;c.docType&&(u[c.docType]||(u[c.docType]=[]),u[c.docType].push(c))}return u}createChunks(t,c){let u=[],d=t.sort((t,c)=>t._id.localeCompare(c._id));for(let t=0;t<d.length;t+=this.config.chunkSize){let m=d.slice(t,t+this.config.chunkSize),g=`${c}-${String(Math.floor(t/this.config.chunkSize)).padStart(4,`0`)}`;u.push({id:g,docType:c,startKey:m[0]._id,endKey:m[m.length-1]._id,documentCount:m.length,path:`chunks/${g}.json`})}return u}prepareChunkData(t,c,u){let d=c.sort((t,c)=>t._id.localeCompare(c._id));for(let c of t){let t=d.filter(t=>t._id>=c.startKey&&t._id<=c.endKey).map(t=>{let c={...t};return delete c._rev,this.config.includeAttachments&&c._attachments?c._attachments=this.transformAttachmentStubs(c._attachments,c._id):this.config.includeAttachments||delete c._attachments,c});u.set(c.id,t)}}async buildIndices(t,c,u){let d=[];if(t.CARD){let c=await this.buildEloIndex(t.CARD,u);d.push(c)}if(t.TAG){let c=await this.buildTagIndex(t.TAG,u);d.push(c)}for(let t of c)for(let[c,m]of Object.entries(t.views))if(m.map){logger.info(`Processing view: ${t._id}/${c}`);let m=await this.buildViewIndex(c,t,u);m?(d.push(m),logger.info(`Successfully built index: ${m.name}`)):logger.warn(`Skipped view index: ${t._id}/${c}`)}return d}async buildEloIndex(t,c){let u=[];for(let c of t)c.elo?.global?.score&&u.push({elo:c.elo.global.score,cardId:c._id});u.sort((t,c)=>t.elo-c.elo);let d={},m=50;for(let t of u){let c=Math.floor(t.elo/50)*50;d[c]||(d[c]=[]),d[c].push(t.cardId)}return c.set(`elo`,{sorted:u,buckets:d,stats:{min:u[0]?.elo||0,max:u[u.length-1]?.elo||0,count:u.length}}),{name:`elo`,type:`btree`,path:`indices/elo.json`}}async buildTagIndex(t,c){let u={};for(let c of t)u[c.name]={cardIds:c.taggedCards,snippet:c.snippet,count:c.taggedCards.length};let d={};for(let c of t)for(let t of c.taggedCards)d[t]||(d[t]=[]),d[t].push(c.name);return c.set(`tags`,{byTag:u,byCard:d}),{name:`tags`,type:`hash`,path:`indices/tags.json`}}async buildViewIndex(t,c,u){if(!this.sourceDB)return logger.error(`Source database not available for view querying`),null;try{let d=`${c._id.replace(`_design/`,``)}/${t}`;logger.info(`Querying CouchDB view: ${d}`);let m=await this.sourceDB.query(d,{include_docs:!1});if(!m.rows||m.rows.length===0)return logger.warn(`View ${d} returned no results`),null;logger.info(`Successfully queried view ${d}: ${m.rows.length} results`);let g=this.formatViewResults(t,m.rows,c),b=`view-${c._id.replace(`_design/`,``)}-${t}`;return u.set(b,g),{name:b,type:`view`,path:`indices/${b}.json`}}catch(u){return logger.error(`Failed to query view ${c._id}/${t}:`,u),null}}formatViewResults(t,c,u){let d={type:`couchdb-view`,viewName:t,designDoc:u._id,results:c,metadata:{resultCount:c.length,generatedAt:new Date().toISOString()}};switch(t){case`elo`:return this.formatEloViewIndex(c,d);case`getTags`:return this.formatTagsViewIndex(c,d);case`cardsByInexperience`:return this.formatInexperienceViewIndex(c,d);default:return this.formatGenericViewIndex(c,d)}}formatEloViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{min:u[0]?.key||0,max:u[u.length-1]?.key||0,count:u.length}}}formatTagsViewIndex(t,c){let u={};for(let c of t){let t=c.key;typeof t==`string`&&(u[t]||(u[t]=[]),u[t].push(c.id))}return{...c,byTag:u,tagCount:Object.keys(u).length}}formatInexperienceViewIndex(t,c){let u=t.sort((t,c)=>typeof t.key==`number`&&typeof c.key==`number`?t.key-c.key:0);return{...c,sorted:u,stats:{minInexperience:u[0]?.key||0,maxInexperience:u[u.length-1]?.key||0,count:u.length}}}formatGenericViewIndex(t,c){return{...c}}async extractAllAttachments(t,c){logger.info(`Extracting attachments...`);let u=[];for(let c of Object.values(t))u.push(...c);let d=u.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);if(d.length===0){logger.info(`No attachments found`);return}logger.info(`Found ${d.length} documents with attachments`);let m=d.map(t=>this.extractDocumentAttachments(t,c));await Promise.all(m),logger.info(`Extracted ${c.size} attachment files`)}async extractDocumentAttachments(t,c){if(!t._attachments||!this.sourceDB)return;let u=t._id;for(let[d,m]of Object.entries(t._attachments))try{let t=await this.sourceDB.getAttachment(u,d),g;if(t instanceof ArrayBuffer)g=Buffer.from(t);else if(Buffer.isBuffer(t))g=t;else{let c=t;g=Buffer.from(await c.arrayBuffer())}let b=`${d}${this.getFileExtension(m.content_type)}`,S=`attachments/${u}/${b}`;c.set(S,{docId:u,attachmentName:d,filename:b,path:S,contentType:m.content_type,length:m.length||g.length,digest:m.digest,buffer:g}),logger.debug(`Extracted attachment: ${S}`)}catch(t){throw logger.error(`Failed to extract attachment ${u}/${d}:`,t),Error(`Failed to extract attachment ${u}/${d}: ${t}`)}}transformAttachmentStubs(t,c){let u={};for(let[d,m]of Object.entries(t))u[d]={path:`attachments/${c}/${`${d}${this.getFileExtension(m.content_type)}`}`,content_type:m.content_type,length:m.length,digest:m.digest,stub:!1};return u}getFileExtension(t){return{"image/jpeg":`.jpg`,"image/jpg":`.jpg`,"image/png":`.png`,"image/gif":`.gif`,"image/webp":`.webp`,"audio/mpeg":`.mp3`,"audio/mp3":`.mp3`,"audio/wav":`.wav`,"audio/ogg":`.ogg`,"video/mp4":`.mp4`,"video/webm":`.webm`,"application/pdf":`.pdf`,"text/plain":`.txt`,"application/json":`.json`}[t]||``}},init_logger(),DEFAULT_MIGRATION_OPTIONS={chunkBatchSize:100,validateRoundTrip:!1,cleanupOnFailure:!0,timeout:3e5},init_logger(),FileSystemError=class extends Error{constructor(t,c,u,d){super(t),this.operation=c,this.filePath=u,this.cause=d,this.name=`FileSystemError`}},nodeFS2=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS2=eval(`require`)(`fs`),nodeFS2.promises=nodeFS2.promises||eval(`require`)(`fs`).promises)}catch{}nodeFS3=null,nodePath=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS3=eval(`require`)(`fs`),nodePath=eval(`require`)(`path`),nodeFS3.promises=nodeFS3.promises||eval(`require`)(`fs`).promises)}catch{}StaticToCouchDBMigrator=class{constructor(t={},c){_defineProperty$2(this,`options`,void 0),_defineProperty$2(this,`progressCallback`,void 0),_defineProperty$2(this,`fs`,void 0),this.options={...DEFAULT_MIGRATION_OPTIONS,...t},this.fs=c}setProgressCallback(t){this.progressCallback=t}async migrateCourse(t,c){let u=Date.now(),d={success:!1,documentsRestored:0,attachmentsRestored:0,designDocsRestored:0,courseConfigRestored:0,errors:[],warnings:[],migrationTime:0};try{logger.info(`Starting migration from ${t} to CouchDB`),this.reportProgress(`manifest`,0,1,`Validating static course...`);let m=await validateStaticCourse(t,this.fs);if(!m.valid)throw d.errors.push(...m.errors),Error(`Static course validation failed: ${m.errors.join(`, `)}`);d.warnings.push(...m.warnings),this.reportProgress(`manifest`,1,1,`Loading course manifest...`);let g=await this.loadManifest(t);logger.info(`Loaded manifest for course: ${g.courseId} (${g.courseName})`),this.reportProgress(`design_docs`,0,g.designDocs.length,`Restoring design documents...`);let b=await this.restoreDesignDocuments(g.designDocs,c);d.designDocsRestored=b.restored,d.errors.push(...b.errors),d.warnings.push(...b.warnings),this.reportProgress(`course_config`,0,1,`Restoring CourseConfig document...`);let S=await this.restoreCourseConfig(g,c);d.courseConfigRestored=S.restored,d.errors.push(...S.errors),d.warnings.push(...S.warnings),this.reportProgress(`course_config`,1,1,`CourseConfig document restored`);let C=this.calculateExpectedCounts(g);this.reportProgress(`documents`,0,g.documentCount,`Aggregating documents from chunks...`);let w=await this.aggregateDocuments(t,g),T=w.filter(t=>t._id!==`CourseConfig`);w.length!==T.length&&d.warnings.push(`Filtered out ${w.length-T.length} CourseConfig document(s) from chunks to prevent conflicts`),this.reportProgress(`documents`,T.length,g.documentCount,`Uploading documents to CouchDB...`);let E=await this.uploadDocuments(T,c);d.documentsRestored=E.restored,d.errors.push(...E.errors),d.warnings.push(...E.warnings);let D=w.filter(t=>t._attachments&&Object.keys(t._attachments).length>0);this.reportProgress(`attachments`,0,D.length,`Uploading attachments...`);let O=await this.uploadAttachments(t,D,c);if(d.attachmentsRestored=O.restored,d.errors.push(...O.errors),d.warnings.push(...O.warnings),this.options.validateRoundTrip){this.reportProgress(`validation`,0,1,`Validating migration...`);let t=await validateMigration(c,C,g);t.valid||(d.warnings.push(`Migration validation found issues`),t.issues.forEach(t=>{t.type===`error`?d.errors.push(`Validation: ${t.message}`):d.warnings.push(`Validation: ${t.message}`)})),this.reportProgress(`validation`,1,1,`Migration validation completed`)}d.success=d.errors.length===0,d.migrationTime=Date.now()-u,logger.info(`Migration completed in ${d.migrationTime}ms`),logger.info(`Documents restored: ${d.documentsRestored}`),logger.info(`Attachments restored: ${d.attachmentsRestored}`),logger.info(`Design docs restored: ${d.designDocsRestored}`),logger.info(`CourseConfig restored: ${d.courseConfigRestored}`),d.errors.length>0&&logger.error(`Migration completed with ${d.errors.length} errors`),d.warnings.length>0&&logger.warn(`Migration completed with ${d.warnings.length} warnings`)}catch(t){d.success=!1,d.migrationTime=Date.now()-u;let m=t instanceof Error?t.message:String(t);if(d.errors.push(`Migration failed: ${m}`),logger.error(`Migration failed:`,t),this.options.cleanupOnFailure)try{await this.cleanupFailedMigration(c)}catch(t){logger.error(`Failed to cleanup after migration failure:`,t),d.warnings.push(`Failed to cleanup after migration failure`)}}return d}async loadManifest(t){try{let c,u;if(this.fs)u=this.fs.joinPath(t,`manifest.json`),c=await this.fs.readFile(u);else if(u=nodeFS3&&nodePath?nodePath.join(t,`manifest.json`):`${t}/manifest.json`,nodeFS3&&this.isLocalPath(t))c=await nodeFS3.promises.readFile(u,`utf8`);else{let t=await fetch(u);if(!t.ok)throw Error(`Failed to fetch manifest: ${t.status} ${t.statusText}`);c=await t.text()}let d=JSON.parse(c);if(!d.version||!d.courseId||!d.chunks)throw Error(`Invalid manifest structure`);return d}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load manifest: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async restoreDesignDocuments(t,c){let u={restored:0,errors:[],warnings:[]};for(let d=0;d<t.length;d++){let m=t[d];this.reportProgress(`design_docs`,d,t.length,`Restoring ${m._id}...`);try{let t;try{t=await c.get(m._id)}catch{}let d={_id:m._id,views:m.views};t?(d._rev=t._rev,logger.debug(`Updating existing design document: ${m._id}`)):logger.debug(`Creating new design document: ${m._id}`),await c.put(d),u.restored++}catch(t){let c=`Failed to restore design document ${m._id}: ${t instanceof Error?t.message:String(t)}`;u.errors.push(c),logger.error(c)}}return this.reportProgress(`design_docs`,t.length,t.length,`Restored ${u.restored} design documents`),u}async aggregateDocuments(t,c){let u=[],d=new Map;for(let m=0;m<c.chunks.length;m++){let g=c.chunks[m];this.reportProgress(`documents`,u.length,c.documentCount,`Loading chunk ${g.id}...`);try{let c=await this.loadChunk(t,g);for(let t of c){if(!t._id){logger.warn(`Document without _id found in chunk ${g.id}, skipping`);continue}d.has(t._id)&&logger.warn(`Duplicate document ID found: ${t._id}, using latest version`),d.set(t._id,t)}}catch(t){throw Error(`Failed to load chunk ${g.id}: ${t instanceof Error?t.message:String(t)}`)}}return u.push(...d.values()),logger.info(`Aggregated ${u.length} unique documents from ${c.chunks.length} chunks`),u}async loadChunk(t,c){try{let u,d;if(this.fs)d=this.fs.joinPath(t,c.path),u=await this.fs.readFile(d);else if(d=nodeFS3&&nodePath?nodePath.join(t,c.path):`${t}/${c.path}`,nodeFS3&&this.isLocalPath(t))u=await nodeFS3.promises.readFile(d,`utf8`);else{let t=await fetch(d);if(!t.ok)throw Error(`Failed to fetch chunk: ${t.status} ${t.statusText}`);u=await t.text()}let m=JSON.parse(u);if(!Array.isArray(m))throw Error(`Chunk file does not contain an array of documents`);return m}catch(t){let c=t instanceof FileSystemError?t.message:`Failed to load chunk: ${t instanceof Error?t.message:String(t)}`;throw Error(c)}}async uploadDocuments(t,c){let u={restored:0,errors:[],warnings:[]},d=this.options.chunkBatchSize;for(let m=0;m<t.length;m+=d){let g=t.slice(m,m+d);this.reportProgress(`documents`,m,t.length,`Uploading batch ${Math.floor(m/d)+1}...`);try{let t=g.map(t=>{let c={...t};return delete c._rev,delete c._attachments,c}),d=await c.bulkDocs(t);for(let t=0;t<d.length;t++){let c=d[t],m=g[t];if(`error`in c){let t=`Failed to upload document ${m._id}: ${c.error} - ${c.reason}`;u.errors.push(t),logger.error(t)}else u.restored++}}catch(t){let c;c=t instanceof Error||t&&typeof t==`object`&&`message`in t?`Failed to upload document batch starting at index ${m}: ${t.message}`:`Failed to upload document batch starting at index ${m}: ${JSON.stringify(t)}`,u.errors.push(c),logger.error(c)}}return this.reportProgress(`documents`,t.length,t.length,`Uploaded ${u.restored} documents`),u}async uploadAttachments(t,c,u){let d={restored:0,errors:[],warnings:[]},m=0;for(let g of c)if(this.reportProgress(`attachments`,m,c.length,`Processing attachments for ${g._id}...`),m++,g._attachments)for(let[c,m]of Object.entries(g._attachments))try{let b=await this.uploadSingleAttachment(t,g._id,c,m,u);b.success?d.restored++:d.errors.push(b.error||`Unknown attachment upload error`)}catch(t){let u=`Failed to upload attachment ${g._id}/${c}: ${t instanceof Error?t.message:String(t)}`;d.errors.push(u),logger.error(u)}return this.reportProgress(`attachments`,c.length,c.length,`Uploaded ${d.restored} attachments`),d}async uploadSingleAttachment(t,c,u,d,m){let g={success:!1,attachmentName:u,docId:c};try{if(!d.path)return g.error=`Attachment metadata missing file path`,g;let b,S;if(this.fs)S=this.fs.joinPath(t,d.path),b=await this.fs.readBinary(S);else if(S=nodeFS3&&nodePath?nodePath.join(t,d.path):`${t}/${d.path}`,nodeFS3&&this.isLocalPath(t))b=await nodeFS3.promises.readFile(S);else{let t=await fetch(S);if(!t.ok)return g.error=`Failed to fetch attachment: ${t.status} ${t.statusText}`,g;b=await t.arrayBuffer()}let C=await m.get(c);await m.putAttachment(c,u,C._rev,b,d.content_type),g.success=!0}catch(t){g.error=t instanceof Error?t.message:String(t)}return g}async restoreCourseConfig(t,c){let u={restored:0,errors:[],warnings:[]};try{if(!t.courseConfig)return u.warnings.push(`No courseConfig found in manifest, skipping CourseConfig document creation`),u;let d={_id:`CourseConfig`,...t.courseConfig,courseID:t.courseId};delete d._rev,await c.put(d),u.restored=1,logger.info(`CourseConfig document created for course: ${t.courseId}`)}catch(t){let c=t instanceof Error?t.message:JSON.stringify(t);u.errors.push(`Failed to restore CourseConfig: ${c}`),logger.error(`CourseConfig restoration failed:`,t)}return u}calculateExpectedCounts(t){let c={};for(let u of t.chunks)c[u.docType]=(c[u.docType]||0)+u.documentCount;return t.designDocs.length>0&&(c._design=t.designDocs.length),c}async cleanupFailedMigration(t){logger.info(`Cleaning up failed migration...`);try{let c=(await t.allDocs()).rows.map(t=>({_id:t.id,_rev:t.value.rev,_deleted:!0}));c.length>0&&(await t.bulkDocs(c),logger.info(`Cleaned up ${c.length} documents from failed migration`))}catch(t){throw logger.error(`Failed to cleanup documents:`,t),t}}reportProgress(t,c,u,d){this.progressCallback&&this.progressCallback({phase:t,current:c,total:u,message:d})}isLocalPath(t){return!t.startsWith(`http://`)&&!t.startsWith(`https://`)}},init_dataDirectory(),init_navigators(),init_Pipeline(),QuotaRoundRobinMixer=class{mix(t,c){if(t.length===0)return[];let u=Math.ceil(c/t.length),d=t.map(t=>[...t.weighted].sort((t,c)=>c.score-t.score).slice(0,u));for(let t=d.length-1;t>0;t--){let c=Math.floor(Math.random()*(t+1));[d[t],d[c]]=[d[c],d[t]]}let m=[],g=0,b=Array(d.length).fill(0);for(;m.length<c&&g<d.length;){g=0;for(let t=0;t<d.length&&!(m.length>=c);t++)b[t]<d[t].length?(m.push(d[t][b[t]]),b[t]++):g++}return m}},init_logger(),init_navigators(),MAX_RUNS2=10,runHistory2=[],mixerDebugAPI={get runs(){return[...runHistory2]},showRun(t=0){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let c;if(typeof t==`number`){if(c=runHistory2[t],!c){logger.info(`[Mixer Debug] No run found at index ${t}. History length: ${runHistory2.length}`);return}}else if(c=runHistory2.find(c=>c.runId.endsWith(t)),!c){logger.info(`[Mixer Debug] No run found matching ID '${t}'.`);return}printMixerSummary(c)},showLastMix(){this.showRun(0)},explainSourceBalance(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let t=runHistory2[0];console.group(`⚖️ Source Balance Analysis`),logger.info(`Mixer: ${t.mixerType}`),logger.info(`Requested limit: ${t.requestedLimit}`),t.quotaPerSource&&logger.info(`Quota per source: ${t.quotaPerSource}`),console.group(`Input Distribution:`);for(let c of t.sourceSummaries){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Provided: ${c.totalCards} cards (${c.reviewCount} reviews, ${c.newCount} new)`),logger.info(` Score range: [${c.scoreRange[0].toFixed(2)}, ${c.scoreRange[1].toFixed(2)}]`)}console.groupEnd(),console.group(`Selection Results:`);for(let c of t.sourceBreakdowns){let t=c.sourceName||c.sourceId;logger.info(`${t}:`),logger.info(` Selected: ${c.totalSelected}/${c.reviewsProvided+c.newProvided} (${c.selectionRate.toFixed(1)}%)`),logger.info(` Reviews: ${c.reviewsSelected}/${c.reviewsProvided}`),logger.info(` New: ${c.newSelected}/${c.newProvided}`),c.reviewsProvided>0&&c.reviewsSelected===0&&logger.info(` ⚠️ Had reviews but none selected!`),c.totalSelected===0&&c.reviewsProvided+c.newProvided>0&&logger.info(` ⚠️ Had cards but none selected!`)}console.groupEnd();let c=t.sourceBreakdowns.map(t=>t.selectionRate),u=c.reduce((t,c)=>t+c,0)/c.length,d=Math.max(...c.map(t=>Math.abs(t-u)));d>20&&(logger.info(`
|
|
411
411
|
\u26A0\uFE0F Significant imbalance detected (max deviation: ${d.toFixed(1)}%)`),logger.info(`Possible causes:`),logger.info(` - Score range differences between sources`),logger.info(` - One source has much better quality cards`),logger.info(` - Different card availability (reviews vs new)`)),console.groupEnd()},compareScores(){if(runHistory2.length===0){logger.info(`[Mixer Debug] No runs captured yet.`);return}let t=runHistory2[0];console.group(`📊 Score Distribution Comparison`),console.table(t.sourceSummaries.map(t=>({source:t.sourceName||t.sourceId,cards:t.totalCards,min:t.bottomScore.toFixed(3),max:t.topScore.toFixed(3),avg:t.avgScore.toFixed(3),range:(t.topScore-t.bottomScore).toFixed(3)})));let c=t.sourceSummaries.map(t=>t.topScore-t.bottomScore),u=t.sourceSummaries.map(t=>t.avgScore),d=Math.max(...c)-Math.min(...c),m=Math.max(...u)-Math.min(...u);(d>.3||m>.2)&&(logger.info(`
|
|
@@ -480,7 +480,7 @@ Displayable Constructor was called with no view Data.
|
|
|
480
480
|
${this.sessionController?.toString()||`Session controller not initialized`}
|
|
481
481
|
User courses: ${this.contentSources.filter(t=>t.type===`course`).map(t=>t.id).toString()}
|
|
482
482
|
User classrooms: ${t.map(t=>t._id).toString()||`No classrooms`}
|
|
483
|
-
`)}catch(t){console.error(`[StudySession] Error during final session setup:`,t)}if(this.sessionController)try{this.$emit(`session-started`),this.loadCard(await this.sessionController.nextCard())}catch(t){console.error(`[StudySession] Error loading next card:`,t),this.$emit(`session-error`,{message:`Failed to load study card`,error:t})}else console.error(`[StudySession] Cannot load card: session controller not initialized`),this.$emit(`session-error`,{message:`Study session initialization failed`})},countCardViews(t,c){return this.sessionRecord.filter(u=>u.card.course_id===t&&u.card.card_id===c).length},async processResponse(t){this.$emit(`card-response`,t),this.timerIsActive=!0,t.cardID=this.cardID,t.courseID=this.courseID,this.currentCard.records.push(t),console.log(`[StudySession] StudySession.processResponse is running...`);let c=this.logCardRecord(t).catch(t=>{throw console.error(`[StudySession] putCardRecord failed:`,t),t}),u=1,d=1;if(isQuestionView(this.$refs.cardViewer?.$refs.activeView)){let t=this.$refs.cardViewer.$refs.activeView;u=t.maxAttemptsPerView,d=t.maxSessionViews}let m=this.countCardViews(this.courseID,this.cardID),g=await this.sessionController.submitResponse(t,c,this.userCourseRegDoc,this.currentCard,this.courseID,this.cardID,u,d,m);try{this.handleUIFeedback(g)}catch(t){console.error(`[StudySession] Error handling UI feedback: ${t}.\n\nResult: ${JSON.stringify(g)}`)}if(this.replanPending){let t=this.replanOptions,c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Firing deferred replan (post-submitResponse)${u}`),this.replanPending=!1,this.replanOptions=null,this.sessionController.requestReplan(t??void 0),this.$emit(`replan-requested`)}g.deferred?(console.log(`[StudySession] Deferred advance — stashing action: ${g.nextCardAction}`),this.deferredNextCardAction=g.nextCardAction):g.shouldLoadNextCard&&this.loadCard(await this.sessionController.nextCard(g.nextCardAction)),g.shouldClearFeedbackShadow&&this.clearFeedbackShadow()},handleUIFeedback(t){if(t.isCorrect){if(!this.frameless)try{this.$refs.shadowWrapper&&t.performanceScore!==void 0&&(this.$refs.shadowWrapper.setAttribute(`style`,`--r: ${255*(1-t.performanceScore)}; --g:255`),this.$refs.shadowWrapper.classList.add(`correct`))}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}this.sessionConfig.likesConfetti&&Ht({origin:{y:1,x:.25+.5*Math.random()},disableForReducedMotion:!0,angle:60+60*Math.random()})}else if(!this.frameless)try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.add(`incorrect`)}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}},clearFeedbackShadow(){this.frameless||setTimeout(()=>{try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.remove(`correct`,`incorrect`)}catch(t){console.warn(`[StudySession] Error clearing shadowWrapper style: ${t}`)}},1250)},async logCardRecord(t){console.log(`[StudySession] About to call user.putCardRecord...`);let c=await this.user.putCardRecord(t);return console.log(`[StudySession] user.putCardRecord completed`),c},async loadCard(t){if(this.loading){console.warn(`Attempted to load card while loading another...`);return}if(console.log(`[StudySession] loading: ${JSON.stringify(t)}`),t===null){this.sessionFinished=!0,this.$emit(`session-finished`,this.sessionRecord);return}this.cardType=t.item.status,this.loading=!0,this.cardCount++,this.data=t.data,this.view=markRaw(t.view),this.cardID=t.item.cardID,this.courseID=t.item.courseID,this.card_elo=t.item.elo||1e3,this.sessionRecord.push({card:{course_id:t.item.courseID,card_id:t.item.cardID,card_elo:this.card_elo,tags:t.tags??[]},item:t.item,records:[]}),this.$emit(`card-loaded`,{courseID:t.item.courseID,cardID:t.item.cardID,cardCount:this.cardCount}),this.loading=!1}}}),Wt={key:0,class:`StudySession`},Gt={key:2},Kt={key:3},qt={class:`text-h4`},Jt={key:0},Yt={key:4,ref:`shadowWrapper`,class:`card-transition-container`},Xt={key:0},__name(_sfc_render$15,`_sfc_render`),Zt=F$3(Ut,[[`render`,_sfc_render$15],[`__scopeId`,`data-v-
|
|
483
|
+
`)}catch(t){console.error(`[StudySession] Error during final session setup:`,t)}if(this.sessionController)try{this.$emit(`session-started`),this.loadCard(await this.sessionController.nextCard())}catch(t){console.error(`[StudySession] Error loading next card:`,t),this.$emit(`session-error`,{message:`Failed to load study card`,error:t})}else console.error(`[StudySession] Cannot load card: session controller not initialized`),this.$emit(`session-error`,{message:`Study session initialization failed`})},countCardViews(t,c){return this.sessionRecord.filter(u=>u.card.course_id===t&&u.card.card_id===c).length},async processResponse(t){this.$emit(`card-response`,t),this.timerIsActive=!0,t.cardID=this.cardID,t.courseID=this.courseID,this.currentCard.records.push(t),console.log(`[StudySession] StudySession.processResponse is running...`);let c=this.logCardRecord(t).catch(t=>{throw console.error(`[StudySession] putCardRecord failed:`,t),t}),u=1,d=1;if(isQuestionView(this.$refs.cardViewer?.$refs.activeView)){let t=this.$refs.cardViewer.$refs.activeView;u=t.maxAttemptsPerView,d=t.maxSessionViews}let m=this.countCardViews(this.courseID,this.cardID),g=await this.sessionController.submitResponse(t,c,this.userCourseRegDoc,this.currentCard,this.courseID,this.cardID,u,d,m);try{this.handleUIFeedback(g)}catch(t){console.error(`[StudySession] Error handling UI feedback: ${t}.\n\nResult: ${JSON.stringify(g)}`)}if(this.replanPending){let t=this.replanOptions,c=t?.label,u=c?` [${c}]`:``;console.log(`[StudySession] Firing deferred replan (post-submitResponse)${u}`),this.replanPending=!1,this.replanOptions=null,this.sessionController.requestReplan(t??void 0),this.$emit(`replan-requested`)}g.deferred?(console.log(`[StudySession] Deferred advance — stashing action: ${g.nextCardAction}`),this.deferredNextCardAction=g.nextCardAction):g.shouldLoadNextCard&&this.loadCard(await this.sessionController.nextCard(g.nextCardAction)),g.shouldClearFeedbackShadow&&this.clearFeedbackShadow()},handleUIFeedback(t){if(t.isCorrect){if(!this.frameless)try{this.$refs.shadowWrapper&&t.performanceScore!==void 0&&(this.$refs.shadowWrapper.setAttribute(`style`,`--r: ${255*(1-t.performanceScore)}; --g:255`),this.$refs.shadowWrapper.classList.add(`correct`))}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}this.sessionConfig.likesConfetti&&Ht({origin:{y:1,x:.25+.5*Math.random()},disableForReducedMotion:!0,angle:60+60*Math.random()})}else if(!this.frameless)try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.add(`incorrect`)}catch(t){console.warn(`[StudySession] Error setting shadowWrapper style: ${t}`)}},clearFeedbackShadow(){this.frameless||setTimeout(()=>{try{this.$refs.shadowWrapper&&this.$refs.shadowWrapper.classList.remove(`correct`,`incorrect`)}catch(t){console.warn(`[StudySession] Error clearing shadowWrapper style: ${t}`)}},1250)},async logCardRecord(t){console.log(`[StudySession] About to call user.putCardRecord...`);let c=await this.user.putCardRecord(t);return console.log(`[StudySession] user.putCardRecord completed`),c},async loadCard(t){if(this.loading){console.warn(`Attempted to load card while loading another...`);return}if(console.log(`[StudySession] loading: ${JSON.stringify(t)}`),t===null){this.sessionFinished=!0,this.$emit(`session-finished`,this.sessionRecord);return}this.cardType=t.item.status,this.loading=!0,this.cardCount++,this.data=t.data,this.view=markRaw(t.view),this.cardID=t.item.cardID,this.courseID=t.item.courseID,this.card_elo=t.item.elo||1e3,this.sessionRecord.push({card:{course_id:t.item.courseID,card_id:t.item.cardID,card_elo:this.card_elo,tags:t.tags??[]},item:t.item,records:[]}),this.$emit(`card-loaded`,{courseID:t.item.courseID,cardID:t.item.cardID,cardCount:this.cardCount}),this.loading=!1}}}),Wt={key:0,class:`StudySession`},Gt={key:2},Kt={key:3,class:`session-finished-area`},qt={class:`text-h4`},Jt={key:0},Yt={key:4,ref:`shadowWrapper`,class:`card-transition-container`},Xt={key:0},__name(_sfc_render$15,`_sfc_render`),Zt=F$3(Ut,[[`render`,_sfc_render$15],[`__scopeId`,`data-v-1f4b2d6a`]]),Qt=defineComponent({name:`MultipleChoiceOption`,components:{MarkdownRenderer:defineAsyncComponent(()=>Promise.resolve().then(()=>(init_MarkdownRenderer_kStoDRNE(),MarkdownRenderer_kStoDRNE_exports)).then(t=>t.n))},props:{content:{type:String,required:!0},selected:{type:Boolean,required:!0},number:{type:Number,required:!0},setSelection:{type:Function,required:!0},submit:{type:Function,required:!0},markedWrong:{type:Boolean,required:!0}},computed:{className(){let t;switch(this.number){case 0:t=`bg-red`;break;case 1:t=`bg-purple`;break;case 2:t=`bg-indigo`;break;case 3:t=`bg-light-blue`;break;case 4:t=`bg-teal`;break;case 5:t=`bg-deep-orange`;break;default:t=`bg-grey`;break}if(this.selected&&!this.markedWrong)return`choice selected ${t} lighten-3 elevation-8`;if(!this.selected&&!this.markedWrong)return`choice not-selected ${t} lighten-4 elevation-1`;if(this.selected&&this.markedWrong)return`choice selected grey lighten-2 elevation-8`;if(!this.selected&&this.markedWrong)return`choice not-selected grey lighten-2 elevation-0`;throw Error(`'selected' and 'markedWrong' props in MultipleChoiceOption are in an impossible configuration.`)}},methods:{select(){this.setSelection(this.number)},submitThisOption(){this.markedWrong||(this.select(),this.submit())}}}),__name(_sfc_render$14,`_sfc_render`),$t=F$3(Qt,[[`render`,_sfc_render$14],[`__scopeId`,`data-v-96de7172`]]),en=defineComponent({name:`RadioMultipleChoice`,components:{MultipleChoiceOption:$t},extends:se$3,props:{choiceList:{type:Array,required:!0}},data(){return{currentSelection:-1,incorrectSelections:[],containerRef:null,_registeredHotkeys:[]}},watch:{choiceList:{immediate:!0,handler(t){t?.length&&this.bindKeys()}}},mounted(){this.containerRef&&this.containerRef.focus()},unmounted(){this.unbindKeys()},methods:{forwardSelection(){if(!this.choiceIsWrong(this.choiceList[this.currentSelection])&&this.currentSelection!==-1){let t={choiceList:this.choiceList,selection:this.currentSelection};this.submitAnswer(t).isCorrect||this.incorrectSelections.push(this.currentSelection)}},setSelection(t){t<this.choiceList.length&&(this.currentSelection=t)},incrementSelection(){this.currentSelection===-1?this.currentSelection=Math.ceil(this.choiceList.length/2):this.currentSelection=Math.min(this.choiceList.length-1,this.currentSelection+1)},decrementSelection(){this.currentSelection===-1?this.currentSelection=Math.floor(this.choiceList.length/2-1):this.currentSelection=Math.max(0,this.currentSelection-1)},choiceIsWrong(t){let c=!1;return this.incorrectSelections.forEach(u=>{this.choiceList[u]===t&&(c=!0)}),c},bindKeys(){let t=[{hotkey:`left`,callback:this.decrementSelection,command:`Move selection left`},{hotkey:`right`,callback:this.incrementSelection,command:`Move selection right`},{hotkey:`enter`,callback:this.forwardSelection,command:`Submit selection`},...Array.from({length:this.choiceList.length},(t,c)=>({hotkey:(c+1).toString(),callback:()=>this.setSelection(c),command:`Select ${(t=>{switch(t){case 0:return`first`;case 1:return`second`;case 2:return`third`;case 3:return`fourth`;case 4:return`fifth`;case 5:return`sixth`;default:return`${t+1}th`}})(c)} option`}))];I$2.addBinding(t),this._registeredHotkeys=t.map(t=>t.hotkey)},unbindKeys(){this._registeredHotkeys&&this._registeredHotkeys.forEach(t=>{I$2.removeBinding(t)})}}}),tn={ref:`containerRef`,class:`multipleChoice`},__name(_sfc_render$13,`_sfc_render`),nn=F$3(en,[[`render`,_sfc_render$13]]),rn=defineComponent({name:`TrueFalse`,components:{RadioMultipleChoice:nn},props:{MouseTrap:{type:Object,required:!0},submit:{type:Function,required:!0}}}),an={"data-viewable":`TrueFalse`},__name(_sfc_render$12,`_sfc_render`),on=F$3(rn,[[`render`,_sfc_render$12]]),sn=defineComponent({name:`UserInputNumber`,ref:{},extends:se$3,methods:{mounted(){this.$refs.input.focus()},isNumeric(t){return!isNaN(Number.parseFloat(t))},makeNumeric(t){if(typeof t==`string`)return Number.parseFloat(t);throw Error(`Expected a string, got `+typeof t)}}}),__name(_sfc_render$11,`_sfc_render`),cn=F$3(sn,[[`render`,_sfc_render$11],[`__scopeId`,`data-v-a56dcd1c`]]),ln=defineComponent({name:`CardLoader`,components:{CardViewer:Q$1},props:{sessionOrder:{type:Number,required:!1,default:0},qualified_id:{type:Object,required:!0},viewLookup:{type:Function,required:!0}},data(){return{loading:!0,view:null,data:[],courseID:``,cardID:``}},created(){this.loadCard()},watch:{qualified_id:{immediate:!0,handler(){this.loadCard()}}},methods:{processResponse(t){log$2(`
|
|
484
484
|
Card was displayed at ${t.timeStamp}
|
|
485
485
|
User spent ${t.timeSpent} milliseconds with the card.
|
|
486
486
|
`),this.$emit(`emitResponse`,t)},async loadCard(){let t=this.qualified_id;console.log(`Card Loader displaying: ${t.courseID}::${t.cardID}`),this.loading=!0;let c=t.courseID,u=t.cardID,d=getDataLayer().getCourseDB(c);try{let t=await d.getCourseDoc(u),m=this.viewLookup(t.id_view),g=t.id_displayable_data.map(t=>d.getCourseDoc(t,{attachments:!0,binary:!0})),b=[];for(let t of g){let c=await t;b.unshift(displayableDataToViewData(c))}this.data=b,this.view=markRaw(m),this.cardID=u,this.courseID=c}catch(t){throw Error(`[CardLoader] Error loading card: ${JSON.stringify(t)}, ${t}`)}finally{this.loading=!1,this.$emit(`card-loaded`)}}}}),__name(_sfc_render$10,`_sfc_render`),un=F$3(ln,[[`render`,_sfc_render$10],[`__scopeId`,`data-v-93f758b5`]]),useAuthStore=()=>{let t=getPinia();return t&&setActivePinia(t),defineStore(`auth`,{state:()=>({_user:void 0,loginAndRegistration:{init:!1,loggedIn:!1,regDialogOpen:!1,loginDialogOpen:!1},onLoadComplete:!1}),actions:{async init(){try{this._user=getDataLayer().getUserDB(),this.loginAndRegistration.loggedIn=this._user?this._user.isLoggedIn():!1,this.onLoadComplete=!0,this.loginAndRegistration.init=!0}catch(t){console.error(`Failed to initialize auth store:`,t),this.loginAndRegistration.loggedIn=!1,this.onLoadComplete=!0,this.loginAndRegistration.init=!0}},setLoginDialog(t){this.loginAndRegistration.loginDialogOpen=t},setRegDialog(t){this.loginAndRegistration.regDialogOpen=t},async resetUserData(){try{if(!this._user)throw Error(`No user available for data reset`);let t=await this._user.resetUserData();if(t.status!==`ok`)throw Error(t.error||`Reset failed`);return console.log(`User data reset successfully`),t}catch(t){throw console.error(`Failed to reset user data:`,t),t}}},getters:{currentUser:async()=>getCurrentUser(),isLoggedIn:t=>t.loginAndRegistration.loggedIn,isInitialized:t=>t.loginAndRegistration.init,status:t=>({loggedIn:t.loginAndRegistration.loggedIn,init:t.loginAndRegistration.init,user:t._user})}})()},useConfigStore=()=>{let t=getPinia();return t&&setActivePinia(t),defineStore(`config`,{state:()=>({config:{darkMode:!1,likesConfetti:!1,sessionTimeLimit:5}}),actions:{updateConfig(t){this.config=t},async updateDarkMode(t){this.config.darkMode=t;let c=await getCurrentUser();c&&await c.setConfig({darkMode:t})},async updateLikesConfetti(t){this.config.likesConfetti=t;let c=await getCurrentUser();c&&await c.setConfig({likesConfetti:t})},async updateSessionTimeLimit(t){this.config.sessionTimeLimit=t;let c=await getCurrentUser();c&&await c.setConfig({sessionTimeLimit:t})},async hydrate(){try{let t=await getCurrentUser();if(t){let c=await t.getConfig();console.log(`user config: ${JSON.stringify(c)}`),this.updateConfig(c)}else console.log(`No user logged in, using default config`)}catch(t){console.warn(`Failed to hydrate config store, using defaults:`,t)}},async init(){await this.hydrate()},resetDefaults(){this.config={darkMode:!1,likesConfetti:!1,sessionTimeLimit:5}}}})()},dn=F$3(defineComponent({__name:`UserChip`,props:{showLoginButton:{type:Boolean},redirectToPath:{}},setup(t){let c=useRouter(),u=useAuthStore(),d=useConfigStore(),m=useAuthUI(),g=ref(``),b=ref([]),S=ref(!1),C=ref(``),w=computed(()=>C.value===`reset`),resetDialogState=()=>{C.value=``,S.value=!1},T=computed(()=>b.value.length>0),E=computed(()=>m.config.value||{showLoginRegistration:!0,showLogout:!0,showResetData:!1,logoutLabel:`Log out`,resetLabel:``});onMounted(async()=>{g.value=(await getCurrentUser()).getUsername(),await m.detectSyncStrategy()});let gotoSettings=async()=>{c.push(`/u/${(await getCurrentUser()).getUsername()}`)},gotoStats=async()=>{c.push(`/u/${(await getCurrentUser()).getUsername()}/stats`)},dismiss=t=>{let c=b.value.indexOf(t);b.value.splice(c,1)},logout=async()=>{(await u._user.logout()).ok&&(u.loginAndRegistration={init:!0,loggedIn:!1,regDialogOpen:!1,loginDialogOpen:!1},d.resetDefaults(),c.push(`/home`))},executeReset=async()=>{try{await u.resetUserData(),d.resetDefaults(),resetDialogState(),c.push(`/home`)}catch(t){console.error(`Failed to reset user data:`,t)}};return(t,c)=>{let u=resolveComponent(`v-icon`),d=resolveComponent(`v-avatar`),m=resolveComponent(`v-chip`),D=resolveComponent(`v-list-item-title`),O=resolveComponent(`v-list-item`),yr=resolveComponent(`v-divider`),br=resolveComponent(`v-list`),xr=resolveComponent(`v-menu`),Sr=resolveComponent(`v-badge`),Cr=resolveComponent(`v-card-title`),wr=resolveComponent(`v-text-field`),Tr=resolveComponent(`v-card-text`),Er=resolveComponent(`v-spacer`),Dr=resolveComponent(`v-btn`),Or=resolveComponent(`v-card-actions`),kr=resolveComponent(`v-card`),Ar=resolveComponent(`v-dialog`);return openBlock(),createElementBlock(Fragment,null,[createVNode(Sr,{content:b.value.length,"model-value":T.value,color:`accent`,location:`end top`},{default:withCtx(()=>[createVNode(xr,{location:`bottom end`,transition:`scale-transition`},{activator:withCtx(({props:t})=>[createVNode(m,mergeProps(t,{class:`ma-2`}),{default:withCtx(()=>[createVNode(d,{start:``,class:`bg-primary`},{default:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[4]||(c[4]=[createTextVNode(`mdi-school`)])),_:1})]),_:1}),createTextVNode(` `+toDisplayString(g.value),1)]),_:2},1040)]),default:withCtx(()=>[createVNode(br,null,{default:withCtx(()=>[(openBlock(!0),createElementBlock(Fragment,null,renderList(b.value,t=>(openBlock(),createBlock(O,{key:t,onClick:c=>dismiss(t)},{default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(t),1)]),_:2},1024)]),_:2},1032,[`onClick`]))),128)),b.value.length?(openBlock(),createBlock(yr,{key:0})):createCommentVNode(``,!0),createVNode(O,{onClick:gotoStats},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[5]||(c[5]=[createTextVNode(`mdi-trending-up`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>c[6]||(c[6]=[createTextVNode(`Stats`)])),_:1})]),_:1}),createVNode(O,{onClick:gotoSettings},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[7]||(c[7]=[createTextVNode(`mdi-cog`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>c[8]||(c[8]=[createTextVNode(`Settings`)])),_:1})]),_:1}),E.value.showLogout?(openBlock(),createBlock(O,{key:1,onClick:logout},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[9]||(c[9]=[createTextVNode(`mdi-logout`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(E.value.logoutLabel),1)]),_:1})]),_:1})):createCommentVNode(``,!0),E.value.showResetData?(openBlock(),createBlock(O,{key:2,onClick:c[0]||(c[0]=t=>S.value=!0)},{prepend:withCtx(()=>[createVNode(u,null,{default:withCtx(()=>c[10]||(c[10]=[createTextVNode(`mdi-delete-sweep`)])),_:1})]),default:withCtx(()=>[createVNode(D,null,{default:withCtx(()=>[createTextVNode(toDisplayString(E.value.resetLabel),1)]),_:1})]),_:1})):createCommentVNode(``,!0)]),_:1})]),_:1})]),_:1},8,[`content`,`model-value`]),createVNode(Ar,{modelValue:S.value,"onUpdate:modelValue":c[3]||(c[3]=t=>S.value=t),"max-width":`500px`,persistent:``},{default:withCtx(()=>[createVNode(kr,null,{default:withCtx(()=>[createVNode(Cr,{class:`text-h5 d-flex align-center`},{default:withCtx(()=>[createVNode(u,{color:`warning`,class:`mr-3`},{default:withCtx(()=>c[11]||(c[11]=[createTextVNode(`mdi-alert-circle`)])),_:1}),c[12]||(c[12]=createTextVNode(` Reset All User Data `))]),_:1}),createVNode(Tr,null,{default:withCtx(()=>[c[13]||(c[13]=createBaseVNode(`p`,{class:`mb-4`},`This will permanently delete:`,-1)),c[14]||(c[14]=createBaseVNode(`ul`,{class:`mb-4`},[createBaseVNode(`li`,null,`All course progress and history`),createBaseVNode(`li`,null,`Scheduled card reviews`),createBaseVNode(`li`,null,`Course registrations`),createBaseVNode(`li`,null,`User preferences`)],-1)),c[15]||(c[15]=createBaseVNode(`p`,{class:`mb-4 text-error font-weight-bold`},`This cannot be undone.`,-1)),createVNode(wr,{modelValue:C.value,"onUpdate:modelValue":c[1]||(c[1]=t=>C.value=t),label:`Type "reset" to confirm`,outlined:``,dense:``,onKeyup:c[2]||(c[2]=withKeys(t=>w.value&&executeReset(),[`enter`]))},null,8,[`modelValue`])]),_:1}),createVNode(Or,null,{default:withCtx(()=>[createVNode(Er),createVNode(Dr,{text:``,onClick:resetDialogState},{default:withCtx(()=>c[16]||(c[16]=[createTextVNode(`Cancel`)])),_:1}),createVNode(Dr,{color:`error`,disabled:!w.value,onClick:executeReset},{default:withCtx(()=>c[17]||(c[17]=[createTextVNode(` Reset All Data `)])),_:1},8,[`disabled`])]),_:1})]),_:1})]),_:1},8,[`modelValue`])],64)}}}),[[`__scopeId`,`data-v-9a38a213`]]),fn={class:`d-flex flex-column align-start`},pn={class:`mb-2`},mn=F$3(defineComponent({__name:`UserLogin`,props:{redirectTo:{default:`/study`}},emits:[`toggle`,`loginSuccess`,`forgotPassword`],setup(t,{emit:c}){let u=t,d=c,m=useRouter(),g=useRoute(),b=useAuthStore(),S=useConfigStore(),C=ref(``),w=ref(``),T=ref(!1),E=ref(!1),D=ref(!1),O=ref(7e3),yr=ref(void 0),br=computed(()=>g.name===`login`),xr=computed(()=>({color:D.value?`error`:`success`,text:D.value?`Try again`:`Log In`})),initBadLogin=()=>{D.value=!0,Z$2({text:`Username or password was incorrect.`,status:Status.error,timeout:O.value}),setTimeout(()=>{D.value=!1},O.value)},login=async()=>{E.value=!0,log$2(`Starting login attempt`),log$2(`Login attempt for username: ${C.value}`);try{log$2(`Attempting to get User instance`),yr.value=await getCurrentUser(),log$2(`Got User instance, attempting login`),await yr.value.login(C.value,w.value),log$2(`Login successful`),log$2(`Initializing user config`),S.init(),log$2(`User config initialized`),log$2(`Setting authentication state`),b.loginAndRegistration.loggedIn=!0,log$2(`Authentication state set, redirecting to: ${u.redirectTo}`),d(`loginSuccess`,u.redirectTo),m.push(u.redirectTo),log$2(`Login and redirect complete`)}catch(t){log$2(`Login attempt failed`),log$2(`Login error details: ${JSON.stringify(t)}`),console.log(`login error: ${JSON.stringify(t)}`),log$2(`Initiating bad login feedback`),initBadLogin()}log$2(`Resetting awaiting response state`),E.value=!1},toggle=()=>{log$2(`Toggling registration / login forms.`),d(`toggle`)},handleForgotPassword=()=>{log$2(`Forgot password clicked`),br.value?m.push(`/request-reset`):d(`forgotPassword`)};return(t,c)=>{let u=resolveComponent(`v-card-title`),d=resolveComponent(`v-text-field`),m=resolveComponent(`v-btn`),g=resolveComponent(`v-snackbar`),b=resolveComponent(`v-icon`),S=resolveComponent(`router-link`),yr=resolveComponent(`v-form`),Sr=resolveComponent(`v-card-text`),Cr=resolveComponent(`v-card`);return openBlock(),createBlock(Cr,null,{default:withCtx(()=>[br.value?createCommentVNode(``,!0):(openBlock(),createBlock(u,{key:0,class:`text-h5 bg-grey-lighten-2`},{default:withCtx(()=>c[5]||(c[5]=[createTextVNode(`Log In`)])),_:1})),createVNode(Sr,null,{default:withCtx(()=>[createVNode(yr,{onsubmit:`return false;`,onSubmit:withModifiers(login,[`prevent`])},{default:withCtx(()=>[createVNode(d,{id:``,modelValue:C.value,"onUpdate:modelValue":c[0]||(c[0]=t=>C.value=t),autofocus:``,name:`username`,label:`Username`,"prepend-icon":`mdi-account-circle`},null,8,[`modelValue`]),createVNode(d,{modelValue:w.value,"onUpdate:modelValue":c[1]||(c[1]=t=>w.value=t),"prepend-icon":`mdi-lock`,name:`password`,hover:`Show password input`,label:`Enter your password`,hint:``,min:`0`,"append-icon":T.value?`mdi-eye-off`:`mdi-eye`,type:T.value?`text`:`password`,"onClick:append":c[2]||(c[2]=()=>T.value=!T.value)},null,8,[`modelValue`,`append-icon`,`type`]),createVNode(g,{modelValue:D.value,"onUpdate:modelValue":c[4]||(c[4]=t=>D.value=t),location:`bottom right`,timeout:O.value},{default:withCtx(()=>[c[7]||(c[7]=createTextVNode(` Username or password was incorrect. `)),createVNode(m,{color:`pink`,variant:`text`,onClick:c[3]||(c[3]=t=>D.value=!1)},{default:withCtx(()=>c[6]||(c[6]=[createTextVNode(`Close`)])),_:1})]),_:1},8,[`modelValue`,`timeout`]),createBaseVNode(`div`,fn,[createBaseVNode(`div`,pn,[createVNode(m,{class:`mr-2`,type:`submit`,loading:E.value,color:xr.value.color},{default:withCtx(()=>[createVNode(b,{start:``},{default:withCtx(()=>c[8]||(c[8]=[createTextVNode(`mdi-lock-open`)])),_:1}),c[9]||(c[9]=createTextVNode(` Log In `))]),_:1},8,[`loading`,`color`]),br.value?(openBlock(),createBlock(S,{key:0,to:`signup`},{default:withCtx(()=>[createVNode(m,{variant:`text`},{default:withCtx(()=>c[10]||(c[10]=[createTextVNode(`Create New Account`)])),_:1})]),_:1})):(openBlock(),createBlock(m,{key:1,variant:`text`,onClick:toggle},{default:withCtx(()=>c[11]||(c[11]=[createTextVNode(`Create New Account`)])),_:1}))]),renderSlot(t.$slots,`forgot-password`,{},()=>[createBaseVNode(`a`,{href:`#`,class:`text-caption text-decoration-none`,onClick:withModifiers(handleForgotPassword,[`prevent`])},` Forgot password? `)],!0)])]),_:3})]),_:3})]),_:3})}}}),[[`__scopeId`,`data-v-563b0048`]]),hn=defineComponent({name:`UserRegistration`,props:{onSignupSuccess:{type:Function,required:!1}},emits:[`toggle`,`signup-success`],data(){return{email:``,username:``,password:``,retypedPassword:``,passwordVisible:!1,emailError:!1,emailHint:``,usernameValidationInProgress:!1,usernameError:!1,usernameHint:``,awaitingResponse:!1,badLoginAttempt:!1,userSecret:``,secret:`goons`,user:null,roles:[`Student`,`Teacher`,`Author`],student:!0,teacher:!1,author:!1,authStore:useAuthStore()}},computed:{registrationRoute(){return typeof this.$route.name==`string`&&this.$route.name.toLowerCase()===`signup`},buttonStatus(){return{color:this.badLoginAttempt?`error`:`success`,text:this.badLoginAttempt?`Try again`:`Log In`}},passwordError(){return validatePassword(this.password)},passwordRetypeError(){return console.log(`[RTE]`),this.password===this.retypedPassword?``:`Passwords must match.`}},async created(){this.user=await getCurrentUser()},methods:{toggle(){log$2(`Toggling registration / login forms.`),this.$emit(`toggle`)},validateEmail(){this.emailError=!1,this.email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email)?(this.emailError=!0,this.emailHint=`Please enter a valid email address`):this.emailHint=``},validateUsername(){this.usernameError=!1},async createUser(){if(this.awaitingResponse=!0,this.passwordError){Z$2({text:this.passwordError,status:Status.error}),this.awaitingResponse=!1;return}if(log$2(`
|