From edb8665504720bf8f0f89689a08cf8b21ee8e0a0 Mon Sep 17 00:00:00 2001 From: Philip Aguilar Bremer Date: Mon, 15 Apr 2024 17:21:45 +0200 Subject: [PATCH] Markdown Syntax to create a quiz within a scenario (#195) * Add quiz-checkbox component It is now possible to define questions in markdown within scenario steps * Refactor helper text and validation * Add question type "radio" * Resolve linting warnings and errors (code quality) * Run prettier on new html and scss files * Fix linting errors * Fix linting errors * Make validation optional * Improve checkbox validation * Fix linting errors * Refactor quiz * Remove empty constructor * Remove unused variable * Change update method * Disable input for quiz questions after submission * remove ngSubmit * Remove now unused variable * submit forms * Fix validation FormGroup.disable() not only disables the form but also validation. Therefore, we can not rely on FormGroup.valid anymore. * Only submit enabled forms * Validate radio buttons after(!) hitting submit * Fix linting error/warning * Remove commented out code * Rm console.log * Add form typings * Run prettier * Remove unneccessary code * Add error/success message customization * Fix radio quiz validation * Fix validation config * Fix linting errors/warnings * Add optional reset button for quiz component * Run prettier * Fix linting error * Add detailed validation for checkbox component * Make helper/error/success message optional * Add detailed validation for radio quiz type * Fix linting error * Run prettier * Disable submit button if a quiz was submitted and not reset. * Add default success/error msg for validation standard mode * Refactoring * Remove unused imports * Fix radio validation * Remove empty constructor --------- Co-authored-by: Jan-Gerrit Goebel --- src/app/app.module.ts | 15 +- src/app/hf-markdown/hf-markdown.component.ts | 13 ++ src/app/quiz/QuestionParams.ts | 19 +++ src/app/quiz/QuestionType.ts | 6 + src/app/quiz/QuizFormGroup.ts | 9 ++ src/app/quiz/Validation.ts | 6 + src/app/quiz/quiz-base.component.ts | 86 +++++++++++ src/app/quiz/quiz-body.component.html | 25 +++ src/app/quiz/quiz-body.component.scss | 3 + src/app/quiz/quiz-body.component.ts | 22 +++ src/app/quiz/quiz-checkbox.component.html | 26 ++++ src/app/quiz/quiz-checkbox.component.scss | 12 ++ src/app/quiz/quiz-checkbox.component.ts | 92 +++++++++++ src/app/quiz/quiz-label.component.html | 19 +++ src/app/quiz/quiz-label.component.scss | 10 ++ src/app/quiz/quiz-label.component.ts | 19 +++ src/app/quiz/quiz-radio.component.html | 29 ++++ src/app/quiz/quiz-radio.component.scss | 12 ++ src/app/quiz/quiz-radio.component.ts | 70 +++++++++ src/app/quiz/quiz.component.html | 39 +++++ src/app/quiz/quiz.component.scss | 22 +++ src/app/quiz/quiz.component.ts | 152 +++++++++++++++++++ src/dark-theme.css | 21 --- 23 files changed, 705 insertions(+), 22 deletions(-) create mode 100644 src/app/quiz/QuestionParams.ts create mode 100644 src/app/quiz/QuestionType.ts create mode 100644 src/app/quiz/QuizFormGroup.ts create mode 100644 src/app/quiz/Validation.ts create mode 100644 src/app/quiz/quiz-base.component.ts create mode 100644 src/app/quiz/quiz-body.component.html create mode 100644 src/app/quiz/quiz-body.component.scss create mode 100644 src/app/quiz/quiz-body.component.ts create mode 100644 src/app/quiz/quiz-checkbox.component.html create mode 100644 src/app/quiz/quiz-checkbox.component.scss create mode 100644 src/app/quiz/quiz-checkbox.component.ts create mode 100644 src/app/quiz/quiz-label.component.html create mode 100644 src/app/quiz/quiz-label.component.scss create mode 100644 src/app/quiz/quiz-label.component.ts create mode 100644 src/app/quiz/quiz-radio.component.html create mode 100644 src/app/quiz/quiz-radio.component.scss create mode 100644 src/app/quiz/quiz-radio.component.ts create mode 100644 src/app/quiz/quiz.component.html create mode 100644 src/app/quiz/quiz.component.scss create mode 100644 src/app/quiz/quiz.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 23c52ba5..727c5801 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -35,6 +35,10 @@ import { AngularSplitModule } from 'angular-split'; import { HfMarkdownComponent } from './hf-markdown/hf-markdown.component'; import { PrintableComponent } from './printable/printable.component'; import { GargantuaClientFactory } from './services/gargantua.service'; +import { QuizCheckboxComponent } from './quiz/quiz-checkbox.component'; +import { QuizRadioComponent } from './quiz/quiz-radio.component'; +import { QuizBodyComponent } from './quiz/quiz-body.component'; +import { QuizComponent } from './quiz/quiz.component'; import { GuacTerminalComponent } from './scenario/guacTerminal.component'; import { IdeWindowComponent } from './scenario/ideWindow.component'; import { ContextService } from './services/context.service'; @@ -69,6 +73,7 @@ import { eyeHideIcon, clockIcon, } from '@cds/core/icon'; +import { QuizLabelComponent } from './quiz/quiz-label.component'; ClarityIcons.addIcons( layersIcon, @@ -132,6 +137,11 @@ export function jwtOptionsFactory() { ScenarioCardComponent, StepComponent, CtrComponent, + QuizCheckboxComponent, + QuizRadioComponent, + QuizBodyComponent, + QuizComponent, + QuizLabelComponent, VMClaimComponent, AtobPipe, HfMarkdownComponent, @@ -155,7 +165,10 @@ export function jwtOptionsFactory() { sanitize: false, convertHTMLEntities: false, }, - globalParsers: [{ component: CtrComponent }], + globalParsers: [ + { component: CtrComponent }, + { component: QuizComponent }, + ], }), JwtModule.forRoot({ jwtOptionsProvider: { diff --git a/src/app/hf-markdown/hf-markdown.component.ts b/src/app/hf-markdown/hf-markdown.component.ts index 2189b132..35d61faa 100644 --- a/src/app/hf-markdown/hf-markdown.component.ts +++ b/src/app/hf-markdown/hf-markdown.component.ts @@ -104,6 +104,19 @@ export class HfMarkdownComponent implements OnChanges { `; }, + quiz(code: string, quizTitle: string, allowedAttempts?: string) { + const tempAtts = Number(allowedAttempts); + const allowedAtts = isNaN(tempAtts) || tempAtts < 1 ? 1 : tempAtts; + return ` + + + `; + }, + note(code: string, type: string, message: string) { return `
diff --git a/src/app/quiz/QuestionParams.ts b/src/app/quiz/QuestionParams.ts new file mode 100644 index 00000000..308d1879 --- /dev/null +++ b/src/app/quiz/QuestionParams.ts @@ -0,0 +1,19 @@ +import { QuestionType } from './QuestionType'; +import { Validation } from './Validation'; + +export interface QuestionParams { + questionTitle: string; + helperText: string; + questionType: QuestionType; + validation: Validation; + successMsg: string; + errorMsg: string; +} + +export type QuestionParam = + | 'title' + | 'info' + | 'type' + | 'validation' + | 'successMsg' + | 'errorMsg'; diff --git a/src/app/quiz/QuestionType.ts b/src/app/quiz/QuestionType.ts new file mode 100644 index 00000000..e138ea95 --- /dev/null +++ b/src/app/quiz/QuestionType.ts @@ -0,0 +1,6 @@ +export type QuestionType = 'radio' | 'checkbox'; + +export function isQuestionType(value: string): value is QuestionType { + const validValues: string[] = ['radio', 'checkbox']; + return validValues.includes(value); +} diff --git a/src/app/quiz/QuizFormGroup.ts b/src/app/quiz/QuizFormGroup.ts new file mode 100644 index 00000000..1b24dd5b --- /dev/null +++ b/src/app/quiz/QuizFormGroup.ts @@ -0,0 +1,9 @@ +import { FormArray, FormControl, FormGroup } from '@angular/forms'; + +export type QuizCheckboxFormGroup = FormGroup<{ + quiz: FormArray>; +}>; + +export type QuizRadioFormGroup = FormGroup<{ + quiz: FormControl; +}>; diff --git a/src/app/quiz/Validation.ts b/src/app/quiz/Validation.ts new file mode 100644 index 00000000..7f6c52ac --- /dev/null +++ b/src/app/quiz/Validation.ts @@ -0,0 +1,6 @@ +export type Validation = 'none' | 'standard' | 'detailed'; + +export function isValidation(value: string): value is Validation { + const validValues: string[] = ['none', 'standard', 'detailed']; + return validValues.includes(value); +} diff --git a/src/app/quiz/quiz-base.component.ts b/src/app/quiz/quiz-base.component.ts new file mode 100644 index 00000000..8ab0b345 --- /dev/null +++ b/src/app/quiz/quiz-base.component.ts @@ -0,0 +1,86 @@ +import { FormGroup } from '@angular/forms'; +import { ClrForm } from '@clr/angular'; +import { Validation } from './Validation'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; + +@Component({ + template: '', +}) +export abstract class QuizBaseComponent implements OnInit { + @Input() + public options: string; + @Input() + public helperText: string; + @Input() + public title: string; + @Input() + public validation: Validation; + @Input() + public errMsg: string; + @Input() + public successMsg: string; + + @ViewChild(ClrForm, { static: true }) + clrForm: ClrForm; + abstract quizForm: FormGroup; + + public optionTitles: string[] = []; + public isSubmitted = false; + public validSubmission = false; + public validationEnabled: boolean; + + ngOnInit(): void { + this.validationEnabled = this.validation != 'none'; + this.extractQuizOptions(); + this.createQuizForm(); + } + + // This function extracts the different possible answers to a quiz question and identifies correct answers + protected abstract extractQuizOptions(): void; + + // Create the quiz form group + protected abstract createQuizForm(): void; + + public submit() { + this.isSubmitted = true; + if (this.quizForm.invalid) { + this.clrForm.markAsTouched(); + } else { + this.validSubmission = true; + } + this.quizForm.disable(); + } + + public reset() { + this.isSubmitted = false; + this.validSubmission = false; + this.quizForm.reset(); + this.quizForm.enable(); + } + + // returns if the option at the specified index is selected + protected abstract isSelectedOption(index: number): boolean; + + // returns if the option at the specified index is correct + protected abstract isCorrectOption(index: number): boolean; + + // funtion for a label to determine if it should be styled as correctly selected option + public hasCorrectOptionClass(index: number): boolean { + return ( + this.validation == 'detailed' && + this.isSubmitted && + this.isCorrectOption(index) + ); + } + + // funtion for a label to determine if it should be styled as incorrectly selected option + public hasIncorrectOptionClass(index: number): boolean { + return ( + this.validation == 'detailed' && + this.isSubmitted && + !this.validSubmission && + this.isSelectedOption(index) && + !this.isCorrectOption(index) + ); + } +} diff --git a/src/app/quiz/quiz-body.component.html b/src/app/quiz/quiz-body.component.html new file mode 100644 index 00000000..0729ac57 --- /dev/null +++ b/src/app/quiz/quiz-body.component.html @@ -0,0 +1,25 @@ + + {{ + helperText + }} + + + {{ errMsg }} + + + + + {{ successMsg }} + + + diff --git a/src/app/quiz/quiz-body.component.scss b/src/app/quiz/quiz-body.component.scss new file mode 100644 index 00000000..45135540 --- /dev/null +++ b/src/app/quiz/quiz-body.component.scss @@ -0,0 +1,3 @@ +.clr-subtext { + margin-bottom: 0.3rem; +} diff --git a/src/app/quiz/quiz-body.component.ts b/src/app/quiz/quiz-body.component.ts new file mode 100644 index 00000000..cf760646 --- /dev/null +++ b/src/app/quiz/quiz-body.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quiz-body', + templateUrl: 'quiz-body.component.html', + styleUrls: ['quiz-body.component.scss'], +}) +export class QuizBodyComponent { + @Input() + public helperText = ''; + @Input() + public isValid: boolean; + @Input() + public isSubmitted: boolean; + @Input() + public validationEnabled: boolean; + @Input() + public errMsg = ''; + @Input() + public successMsg = ''; +} diff --git a/src/app/quiz/quiz-checkbox.component.html b/src/app/quiz/quiz-checkbox.component.html new file mode 100644 index 00000000..14839cb5 --- /dev/null +++ b/src/app/quiz/quiz-checkbox.component.html @@ -0,0 +1,26 @@ +
+ + + + + + + + +
diff --git a/src/app/quiz/quiz-checkbox.component.scss b/src/app/quiz/quiz-checkbox.component.scss new file mode 100644 index 00000000..5bec2978 --- /dev/null +++ b/src/app/quiz/quiz-checkbox.component.scss @@ -0,0 +1,12 @@ +pre { + display: flex; + flex-direction: column; +} +form { + display: inherit; + flex-direction: column; +} +clr-checkbox-container { + flex-direction: column !important; + margin-top: 0; +} diff --git a/src/app/quiz/quiz-checkbox.component.ts b/src/app/quiz/quiz-checkbox.component.ts new file mode 100644 index 00000000..d279cad6 --- /dev/null +++ b/src/app/quiz/quiz-checkbox.component.ts @@ -0,0 +1,92 @@ +import { Component } from '@angular/core'; +import { + AbstractControl, + FormArray, + NonNullableFormBuilder, + FormControl, + ValidatorFn, +} from '@angular/forms'; +import { QuizCheckboxFormGroup } from './QuizFormGroup'; +import { QuizBaseComponent } from './quiz-base.component'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quiz-checkbox', + templateUrl: 'quiz-checkbox.component.html', + styleUrls: ['quiz-checkbox.component.scss'], +}) +export class QuizCheckboxComponent extends QuizBaseComponent { + public override quizForm: QuizCheckboxFormGroup; + public requiredValues: boolean[] = []; + + constructor(private fb: NonNullableFormBuilder) { + super(); + } + + protected override extractQuizOptions() { + this.options.split('\n- ').forEach((option: string) => { + this.optionTitles.push(option.split(':(')[0]); + const requiredValue = option.split(':(')[1].toLowerCase() === 'x)'; + this.requiredValues.push(requiredValue); + }); + } + + protected override createQuizForm() { + if (this.validationEnabled) { + this.quizForm = this.fb.group( + { + quiz: new FormArray>( + [], + this.validateCheckboxes(), + ), + }, + { updateOn: 'change' }, + ); + } else { + this.quizForm = this.fb.group( + { + quiz: new FormArray>([]), + }, + { updateOn: 'change' }, + ); + } + this.addCheckboxes(); + } + + private addCheckboxes() { + this.optionTitles.forEach(() => + this.optionsFormArray.push(this.fb.control(false)), + ); + } + + private get optionsFormArray(): FormArray> { + return this.quizForm.controls.quiz; + } + + private validateCheckboxes(): ValidatorFn { + return (control: AbstractControl) => { + const formArray = control as FormArray>; + let validatedCheckboxes = true; + formArray.controls.forEach( + (control: FormControl, index: number) => { + validatedCheckboxes = + validatedCheckboxes && control.value === this.requiredValues[index]; + }, + ); + if (!validatedCheckboxes) { + return { + checkboxesValidated: true, + }; + } + return null; + }; + } + + protected override isSelectedOption(index: number): boolean { + return this.optionsFormArray.at(index).value; + } + + protected override isCorrectOption(index: number): boolean { + return this.requiredValues[index]; + } +} diff --git a/src/app/quiz/quiz-label.component.html b/src/app/quiz/quiz-label.component.html new file mode 100644 index 00000000..a6139c58 --- /dev/null +++ b/src/app/quiz/quiz-label.component.html @@ -0,0 +1,19 @@ +{{ optionTitle }} + diff --git a/src/app/quiz/quiz-label.component.scss b/src/app/quiz/quiz-label.component.scss new file mode 100644 index 00000000..a0eaed8a --- /dev/null +++ b/src/app/quiz/quiz-label.component.scss @@ -0,0 +1,10 @@ +span.correct-option { + color: var(--clr-alert-success-font-color, hsl(198, 0%, 40%)); + background-color: var(--clr-alert-success-bg-color, hsl(93, 52%, 88%)); + font-weight: bold; +} + +span.incorrect-option { + color: var(--clr-alert-danger-font-color, hsl(198, 0%, 40%)); + background-color: var(--clr-alert-danger-bg-color, hsl(9, 95%, 92%)); +} diff --git a/src/app/quiz/quiz-label.component.ts b/src/app/quiz/quiz-label.component.ts new file mode 100644 index 00000000..3fbd44c8 --- /dev/null +++ b/src/app/quiz/quiz-label.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quiz-label', + templateUrl: 'quiz-label.component.html', + styleUrls: ['quiz-label.component.scss'], +}) +// This component only contains the quiz label content, not the label selector itself. +// If it contains the label selector itself, +// this would break Clarity's predefined structure of checkbox/radio wrappers. +export class QuizLabelComponent { + @Input() + public optionTitle: string; + @Input() + public hasCorrectOptionClass: boolean; + @Input() + public hasIncorrectOptionClass: boolean; +} diff --git a/src/app/quiz/quiz-radio.component.html b/src/app/quiz/quiz-radio.component.html new file mode 100644 index 00000000..8e9c4769 --- /dev/null +++ b/src/app/quiz/quiz-radio.component.html @@ -0,0 +1,29 @@ +
+ + + + + + + + +
diff --git a/src/app/quiz/quiz-radio.component.scss b/src/app/quiz/quiz-radio.component.scss new file mode 100644 index 00000000..f3b5b8fe --- /dev/null +++ b/src/app/quiz/quiz-radio.component.scss @@ -0,0 +1,12 @@ +pre { + display: flex; + flex-direction: column; +} +form { + display: inherit; + flex-direction: column; +} +clr-radio-container { + flex-direction: column !important; + margin-top: 0; +} diff --git a/src/app/quiz/quiz-radio.component.ts b/src/app/quiz/quiz-radio.component.ts new file mode 100644 index 00000000..391570bc --- /dev/null +++ b/src/app/quiz/quiz-radio.component.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { + AbstractControl, + FormBuilder, + FormControl, + ValidatorFn, +} from '@angular/forms'; +import { QuizRadioFormGroup } from './QuizFormGroup'; +import { QuizBaseComponent } from './quiz-base.component'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quiz-radio', + templateUrl: 'quiz-radio.component.html', + styleUrls: ['quiz-radio.component.scss'], +}) +export class QuizRadioComponent extends QuizBaseComponent { + public override quizForm: QuizRadioFormGroup; + public correctIndex: number; + + constructor(private fb: FormBuilder) { + super(); + } + + protected override extractQuizOptions() { + this.correctIndex = 0; + this.options.split('\n- ').forEach((option: string, index: number) => { + this.optionTitles.push(option.split(':(')[0]); + const requiredValue = option.split(':(')[1].toLowerCase() === 'x)'; + if (requiredValue) { + this.correctIndex = index; + } + }); + } + + protected override createQuizForm() { + this.quizForm = this.fb.group( + { + quiz: new FormControl(null), + }, + { + validators: this.validateRadio(), + updateOn: 'change', + }, + ); + } + + private validateRadio(): ValidatorFn { + return (control: AbstractControl) => { + const formGroup = control as QuizRadioFormGroup; + if ( + this.validationEnabled && + formGroup.controls.quiz.value != this.correctIndex + ) { + return { + quiz: this.correctIndex, + }; + } + return null; + }; + } + + protected override isSelectedOption(index: number): boolean { + return this.quizForm.controls.quiz.value == index; + } + + protected override isCorrectOption(index: number): boolean { + return index == this.correctIndex; + } +} diff --git a/src/app/quiz/quiz.component.html b/src/app/quiz/quiz.component.html new file mode 100644 index 00000000..c0ae71d1 --- /dev/null +++ b/src/app/quiz/quiz.component.html @@ -0,0 +1,39 @@ +
+    
{{ quizTitle }}
+ + + + +
+ + +
+ Allowed attempts left: {{ allowedAtts }} +
diff --git a/src/app/quiz/quiz.component.scss b/src/app/quiz/quiz.component.scss new file mode 100644 index 00000000..f889955c --- /dev/null +++ b/src/app/quiz/quiz.component.scss @@ -0,0 +1,22 @@ +.quiz-container { + display: flex; + flex-direction: column; +} + +.quiz-title { + margin-bottom: 1.2rem; + font-weight: bold; +} + +.button-container { + display: flex; + width: 100%; + flex-direction: row; +} + +.submit-button { + align-self: flex-start; +} +.reset-button { + margin-left: var(--clr-btn-horizontal-margin, 0.6rem); +} diff --git a/src/app/quiz/quiz.component.ts b/src/app/quiz/quiz.component.ts new file mode 100644 index 00000000..3ad640ee --- /dev/null +++ b/src/app/quiz/quiz.component.ts @@ -0,0 +1,152 @@ +import { + Component, + Input, + OnInit, + QueryList, + ViewChildren, +} from '@angular/core'; +import { QuestionParam, QuestionParams } from './QuestionParams'; +import { QuizCheckboxComponent } from './quiz-checkbox.component'; +import { QuizRadioComponent } from './quiz-radio.component'; +import { QuestionType, isQuestionType } from './QuestionType'; +import { isValidation } from './Validation'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'quiz', + templateUrl: 'quiz.component.html', + styleUrls: ['quiz.component.scss'], +}) +export class QuizComponent implements OnInit { + @Input() + public quizTitle: string; + @Input() + public questionsRaw: string; + @Input() + public allowedAtts = 1; + + @ViewChildren('quizCheckbox') + private quizCheckbox: QueryList = new QueryList(); + @ViewChildren('quizRadio') private quizRadio: QueryList = + new QueryList(); + + public questionParams: QuestionParams[] = []; + public questions: string[]; + public isSubmitted = false; + + public ngOnInit() { + this.questions = this.questionsRaw.split('\n---\n'); + this.questions.forEach((question: string) => { + this.questionParams.push(this.getQuizQuestionParams(question)); + }); + } + + public getQuestionType(question: string, questionType: string): QuestionType { + const correctAnswers: number = (question.match(/:\(x\)/g) || []).length; + return questionType.toLowerCase() === 'radio' && correctAnswers === 1 + ? 'radio' + : 'checkbox'; + } + + public getOptions(question: string): string { + return question.split(/\n- (.*)/s)[1]; + } + + public submit() { + this.quizCheckbox.forEach((checkbox: QuizCheckboxComponent) => { + if (checkbox.quizForm.enabled) { + checkbox.submit(); + } + }); + this.quizRadio.forEach((radio: QuizRadioComponent) => { + if (radio.quizForm.enabled) { + radio.submit(); + } + }); + --this.allowedAtts; + this.isSubmitted = true; + } + + public reset() { + this.quizCheckbox.forEach((checkbox: QuizCheckboxComponent) => { + checkbox.reset(); + }); + this.quizRadio.forEach((radio: QuizRadioComponent) => { + radio.reset(); + }); + this.isSubmitted = false; + } + + private getQuizQuestionParams(question: string): QuestionParams { + let defaultErrorMsg = ''; + let defaultSuccessMsg = ''; + const questionTitle = this.getRawQuizQuestionParam(question, 'title') ?? ''; + const helperText = this.getRawQuizQuestionParam(question, 'info') ?? ''; + const questionType = this.getQuizQuestionParam( + question, + 'type', + 'checkbox', + isQuestionType, + ); + const validation = this.getQuizQuestionParam( + question, + 'validation', + 'standard', + isValidation, + ); + // In validation mode 'none', success/error alerts are not shown. Hence, the default for success/error message is ''. + // In validation mode 'detailed', correctly/incorrectly selected answers are highlighted. Hence, success/error message is optional. + // In validation mode 'standard', a success/error alert must be displayed to the user. + // Hence, we fall back to the following default values: + if (validation == 'standard') { + defaultSuccessMsg = 'Correct!'; + defaultErrorMsg = 'Incorrect!'; + } + const successMsg = + this.getRawQuizQuestionParam(question, 'successMsg') ?? defaultSuccessMsg; + const errorMsg = + this.getRawQuizQuestionParam(question, 'errorMsg') ?? defaultErrorMsg; + return { + questionTitle: questionTitle, + helperText: helperText, + questionType: questionType, + validation: validation, + successMsg: successMsg, + errorMsg: errorMsg, + }; + } + + private getRawQuizQuestionParam( + question: string, + questionParam: QuestionParam, + ) { + const regexPattern = `-\\$${questionParam}-:\\s`; + const regex = new RegExp(regexPattern); + let rawQuestionParamValue: string | undefined; + if (regex.test(question)) { + rawQuestionParamValue = question + .split(`-$${questionParam}-: `) + .pop() + ?.split( + /(\n-\$(title|info|type|validation|successMsg|errorMsg)-:\s)|(\n-\s)/, + )[0]; + } + return rawQuestionParamValue; + } + + private getQuizQuestionParam( + question: string, + questionParam: QuestionParam, + defaultVal: T, + validate: (value: string) => value is T, + ): T { + const rawQuestionParamValue = this.getRawQuizQuestionParam( + question, + questionParam, + ); + if (rawQuestionParamValue && validate(rawQuestionParamValue)) { + return rawQuestionParamValue; + } + return defaultVal; + } +} diff --git a/src/dark-theme.css b/src/dark-theme.css index 71ad6130..7fe9c4d3 100644 --- a/src/dark-theme.css +++ b/src/dark-theme.css @@ -561,24 +561,3 @@ cds-icon { color: white; } - -cds-icon { - &[status='success'] { - fill: var(--clr-icon-color-success); - } - &[status='danger'] { - fill: var(--clr-icon-color-error); - } - &[status='warning'] { - fill: var(--clr-icon-color-warning); - } - &[status='info'] { - fill: var(--clr-icon-color-info); - } - &[inverse] { - fill: var(--clr-icon-color-inverse); - } - &[status='highlight'] { - fill: var(--clr-icon-color-highlight); - } -}