diff --git a/convert/convertBooks.ts b/convert/convertBooks.ts index 9b016a5f1..2e3528cf5 100644 --- a/convert/convertBooks.ts +++ b/convert/convertBooks.ts @@ -201,8 +201,9 @@ export async function convertBooks( 'quizzes', book.id + '.json' ), - content: JSON.stringify(convertQuizBook(context, book)) + content: JSON.stringify(convertQuizBook(context, book), null, 2) }); + process.stdout.write(` ${book.id}`); break; default: bookConverted = true; @@ -291,32 +292,34 @@ export async function convertBooks( }; } -type QuizAnswer = { +export type QuizExplanation = { + text?: string; + audio?: string; +}; + +export type QuizAnswer = { //\aw or \ar correct: boolean; text?: string; image?: string; audio?: string; - explanation?: { - //\ae - text: string; - audio?: string; - }; + explanation?: QuizExplanation; }; -type QuizQuestion = { +export type QuizQuestion = { //\qu text: string; image?: string; audio?: string; + columns?: number; //\ac + explanation?: QuizExplanation; answers: QuizAnswer[]; }; -type Quiz = { +export type Quiz = { id: string; //\id name?: string; //\qn shortName?: string; //\qs - columns?: number; //\ac rightAnswerAudio?: string[]; //\ra wrongAnswerAudio?: string[]; //\wa questions: QuizQuestion[]; @@ -343,9 +346,6 @@ function convertQuizBook(context: ConvertBookContext, book: Book): Quiz { id: quizSFM.match(/\\id ([^\\\r\n]+)/i)![1], name: quizSFM.match(/\\qn ([^\\\r\n]+)/i)?.at(1), shortName: quizSFM.match(/\\qs ([^\\\r\n]+)/i)?.at(1), - columns: quizSFM.match(/\\ac ([^\\\r\n]+)/i)?.at(1) - ? parseInt(quizSFM.match(/\\ac ([^\\\r\n]+)/i)![1]) - : undefined, rightAnswerAudio: quizSFM.match(/\\ra ([^\\\r\n]+)/gi)?.map((m) => { return m.match(/\\ra ([^\\\r\n]+)/i)![1]; }), @@ -359,7 +359,7 @@ function convertQuizBook(context: ConvertBookContext, book: Book): Quiz { const parsed = m.match(/([0-9]+)( *- *[0-9]+)? ([^\\\r\n]+)/i)!; return { rangeMin: parseInt(parsed[1]), - rangeMax: parsed[2] ? parseInt(parsed[2]) : undefined, + rangeMax: parsed[2] ? parseInt(parsed[2].replace('-', '')) : parseInt(parsed[1]), message: parsed[3] }; }), @@ -370,8 +370,8 @@ function convertQuizBook(context: ConvertBookContext, book: Book): Quiz { let aCount = 0; let question: QuizQuestion = { text: '', answers: [] }; let answer: QuizAnswer = { correct: false }; - quizSFM.match(/\\(qu|aw|ar|ae) ([^\\\r\n]+)/gi)?.forEach((m) => { - const parsed = m.match(/\\(qu|aw|ar|ae) ([^\\\r\n]+)/i)!; + quizSFM.match(/\\(qu|aw|ar|ae|ac) ([^\\\r\n]+)/gi)?.forEach((m) => { + const parsed = m.match(/\\(qu|aw|ar|ae|ac) ([^\\\r\n]+)/i)!; switch (parsed[1]) { case 'qu': if (aCount > 0) { @@ -408,14 +408,34 @@ function convertQuizBook(context: ConvertBookContext, book: Book): Quiz { aCount++; } break; + case 'ac': + question.columns = parseInt(parsed[2]); + break; case 'ae': - if (!question.answers[aCount - 1].explanation) { - question.answers[aCount - 1].explanation = { text: '' }; - } - if (hasAudioExtension(parsed[2])) { - question.answers[aCount - 1].explanation!.audio = parsed[2]; - } else { - question.answers[aCount - 1].explanation!.text = parsed[2]; + { + const isAudio = hasAudioExtension(parsed[2]); + const hasExplanationsInAnswers = isAudio + ? question.answers.some((answer) => answer.explanation?.audio !== undefined) + : question.answers.some((answer) => answer.explanation?.text != undefined); + + if (aCount == 0) { + // Question-level explanation + question.explanation = updateExplanation(question.explanation, parsed[2]); + } else { + if (aCount == 1 || hasExplanationsInAnswers) { + // Answer-specific explanation + question.answers[aCount - 1].explanation = updateExplanation( + question.answers[aCount - 1].explanation, + parsed[2] + ); + } else { + // Question-level explanation (same for all answers) + question.explanation = updateExplanation( + question.explanation, + parsed[2] + ); + } + } } break; } @@ -424,6 +444,21 @@ function convertQuizBook(context: ConvertBookContext, book: Book): Quiz { return quiz; } +function updateExplanation( + explanation: QuizExplanation | undefined, + text: string +): QuizExplanation { + if (!explanation) { + explanation = {}; + } + if (hasAudioExtension(text)) { + explanation.audio = text; + } else { + explanation.text = text; + } + return explanation; +} + function convertScriptureBook( pk: SABProskomma, context: ConvertBookContext, diff --git a/src/lib/components/BookSelector.svelte b/src/lib/components/BookSelector.svelte index 20ea859db..f2a84c6aa 100644 --- a/src/lib/components/BookSelector.svelte +++ b/src/lib/components/BookSelector.svelte @@ -12,7 +12,10 @@ The navbar component. import config from '$lib/data/config'; import SelectList from './SelectList.svelte'; import * as numerals from '$lib/scripts/numeralSystem'; + import { goto } from '$app/navigation'; + import { base } from '$app/paths'; + export let displayLabel; $: book = $nextRef.book === '' ? $refs.book : $nextRef.book; $: chapter = $nextRef.chapter === '' ? $refs.chapter : $nextRef.chapter; $: verseCount = getVerseCount(chapter, chapters); @@ -29,9 +32,11 @@ The navbar component. $: v = $t.Selector_Verse; let bookSelector; - $: label = config.bookCollections - .find((x) => x.id === $refs.collection) - .books.find((x) => x.id === book).name; + $: label = + displayLabel ?? + config.bookCollections + .find((x) => x.id === $refs.collection) + .books.find((x) => x.id === book).name; function chapterCount(book) { let count = Object.keys(books.find((x) => x.bookCode === book).versesByChapters).length; @@ -48,7 +53,20 @@ The navbar component. /** * Pushes reference changes to nextRef. Pushes final change to default reference. */ + async function navigateReference(e) { + // Handle special book navigation first + if (e.detail.tab === b && e.detail?.url) { + const book = e.detail.text; + addHistory({ + collection: $refs.collection, + book, + chapter: '', + url: e.detail.url + }); + goto(e.detail.url); + return; + } if (!showChapterSelector) { $nextRef.book = e.detail.text; await refs.set({ book: $nextRef.book, chapter: 1 }); @@ -104,6 +122,7 @@ The navbar component. verse: $nextRef.verse }); document.activeElement.blur(); + goto(`${base}/`); } function resetNavigation() { @@ -113,11 +132,19 @@ The navbar component. nextRef.reset(); } - /**list of books in current docSet*/ + /**list of books, quizzes, and quiz groups in current docSet*/ $: books = $refs.catalog.documents; /**list of chapters in current book*/ $: chapters = books.find((d) => d.bookCode === book).versesByChapters; + function getBookUrl(book) { + let url; + if (book.type === 'quiz') { + url = `${base}/quiz/${$refs.collection}/${book.id}`; + } + return url; + } + let bookGridGroup = ({ colId, bookLabel = 'abbreviation' }) => { let groups = []; var lastGroup = null; @@ -125,12 +152,15 @@ The navbar component. config.bookCollections .find((x) => x.id === colId) .books.forEach((book) => { - // Include books only in the catalog (i.e. only supported book types) - if (books.find((x) => x.bookCode === book.id)) { + const url = getBookUrl(book); + if (books.find((x) => x.bookCode === book.id) || url) { let label = book[bookLabel] || book.name; - let cell = { label: label, id: book.id }; + let cell = { label, id: book.id, url }; let group = book.testament || ''; - if ((lastGroup == null || group !== lastGroup) && config.mainFeatures['book-group-titles']) { + if ( + (lastGroup == null || group !== lastGroup) && + config.mainFeatures['book-group-titles'] + ) { // Create new group groups.push({ header: book.testament @@ -143,8 +173,8 @@ The navbar component. lastGroup = group; } else { // Add Book to last group - let cells = groups[groups.length - 1].cells; - groups[groups.length - 1].cells = [...cells, cell]; + let cells = groups.at(-1).cells; + groups.at(-1).cells = [...cells, cell]; } } }); diff --git a/src/lib/components/HistoryCard.svelte b/src/lib/components/HistoryCard.svelte index bd23c1703..3565df44b 100644 --- a/src/lib/components/HistoryCard.svelte +++ b/src/lib/components/HistoryCard.svelte @@ -10,6 +10,7 @@ TODO: import { formatDateAndTime } from '$lib/scripts/dateUtils'; import { base } from '$app/paths'; import config from '$lib/data/config'; + import { goto } from '$app/navigation'; export let history: HistoryItem; $: bc = config.bookCollections.find((x) => x.id === history.collection); @@ -22,21 +23,29 @@ TODO: : history.chapter; $: dateFormat = formatDateAndTime(new Date(history.date)); $: textDirection = bc.style.textDirection; - - -