payload-quiz-plugin 1.0.0
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/LICENSE +35 -0
- package/README.md +413 -0
- package/dist/client.d.mts +303 -0
- package/dist/client.d.ts +303 -0
- package/dist/client.js +769 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +725 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +434 -0
- package/dist/index.d.ts +434 -0
- package/dist/index.js +923 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +902 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +87 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { CollectionConfig, Plugin, Block } from 'payload';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translation keys for the quiz plugin
|
|
5
|
+
*/
|
|
6
|
+
interface QuizTranslations {
|
|
7
|
+
tests?: {
|
|
8
|
+
title?: string;
|
|
9
|
+
titleSingular?: string;
|
|
10
|
+
titlePlural?: string;
|
|
11
|
+
pageTitle?: string;
|
|
12
|
+
startButton?: string;
|
|
13
|
+
finishButton?: string;
|
|
14
|
+
nextButton?: string;
|
|
15
|
+
previousButton?: string;
|
|
16
|
+
noQuestionsAvailable?: string;
|
|
17
|
+
noQuestionsDescription?: string;
|
|
18
|
+
backToTests?: string;
|
|
19
|
+
questionsLabel?: string;
|
|
20
|
+
minutesLabel?: string;
|
|
21
|
+
passMarkLabel?: string;
|
|
22
|
+
beginTestDescription?: string;
|
|
23
|
+
showingRange?: string;
|
|
24
|
+
showingSingle?: string;
|
|
25
|
+
noResults?: string;
|
|
26
|
+
};
|
|
27
|
+
questions?: {
|
|
28
|
+
selectOne?: string;
|
|
29
|
+
selectMultiple?: string;
|
|
30
|
+
loading?: string;
|
|
31
|
+
};
|
|
32
|
+
results?: {
|
|
33
|
+
congratulations?: string;
|
|
34
|
+
keepPracticing?: string;
|
|
35
|
+
youPassed?: string;
|
|
36
|
+
needToPass?: string;
|
|
37
|
+
correct?: string;
|
|
38
|
+
incorrect?: string;
|
|
39
|
+
unanswered?: string;
|
|
40
|
+
timeTaken?: string;
|
|
41
|
+
tryAgain?: string;
|
|
42
|
+
backToTests?: string;
|
|
43
|
+
reviewAnswers?: string;
|
|
44
|
+
question?: string;
|
|
45
|
+
yourAnswer?: string;
|
|
46
|
+
correctAnswer?: string;
|
|
47
|
+
explanation?: string;
|
|
48
|
+
};
|
|
49
|
+
timer?: {
|
|
50
|
+
timeRemaining?: string;
|
|
51
|
+
};
|
|
52
|
+
modal?: {
|
|
53
|
+
unansweredTitle?: string;
|
|
54
|
+
unansweredDescription?: string;
|
|
55
|
+
continueTest?: string;
|
|
56
|
+
finishAnyway?: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* i18n configuration for the quiz plugin
|
|
61
|
+
*/
|
|
62
|
+
interface QuizI18nConfig {
|
|
63
|
+
/**
|
|
64
|
+
* Enable i18n support. When false, uses defaultLocale translations only.
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
enabled?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Default locale to use when i18n is disabled or as fallback
|
|
70
|
+
* @default 'en'
|
|
71
|
+
*/
|
|
72
|
+
defaultLocale?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Custom translations that override or extend plugin defaults.
|
|
75
|
+
* Keys are locale codes (e.g., 'en', 'de', 'fr')
|
|
76
|
+
*/
|
|
77
|
+
translations?: Record<string, QuizTranslations>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Collection customization options
|
|
81
|
+
*/
|
|
82
|
+
interface QuizCollectionOverrides {
|
|
83
|
+
/**
|
|
84
|
+
* Override or extend the Questions collection config
|
|
85
|
+
*/
|
|
86
|
+
questions?: Partial<CollectionConfig>;
|
|
87
|
+
/**
|
|
88
|
+
* Override or extend the Tests collection config
|
|
89
|
+
*/
|
|
90
|
+
tests?: Partial<CollectionConfig>;
|
|
91
|
+
/**
|
|
92
|
+
* Override or extend the CertificateTypes collection config
|
|
93
|
+
*/
|
|
94
|
+
certificateTypes?: Partial<CollectionConfig>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Admin UI configuration
|
|
98
|
+
*/
|
|
99
|
+
interface QuizAdminConfig {
|
|
100
|
+
/**
|
|
101
|
+
* Admin group name for quiz collections
|
|
102
|
+
* @default 'Quiz'
|
|
103
|
+
*/
|
|
104
|
+
group?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Main plugin configuration
|
|
108
|
+
*/
|
|
109
|
+
interface QuizPluginConfig {
|
|
110
|
+
/**
|
|
111
|
+
* i18n configuration
|
|
112
|
+
*/
|
|
113
|
+
i18n?: QuizI18nConfig;
|
|
114
|
+
/**
|
|
115
|
+
* Collection configuration overrides
|
|
116
|
+
*/
|
|
117
|
+
collections?: QuizCollectionOverrides;
|
|
118
|
+
/**
|
|
119
|
+
* Admin panel configuration
|
|
120
|
+
*/
|
|
121
|
+
admin?: QuizAdminConfig;
|
|
122
|
+
/**
|
|
123
|
+
* Enable or disable specific features
|
|
124
|
+
*/
|
|
125
|
+
features?: {
|
|
126
|
+
/**
|
|
127
|
+
* Enable certificate types collection
|
|
128
|
+
* @default true
|
|
129
|
+
*/
|
|
130
|
+
certificateTypes?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Enable the tests archive block
|
|
133
|
+
* @default true
|
|
134
|
+
*/
|
|
135
|
+
testsArchiveBlock?: boolean;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolved plugin configuration with all defaults applied
|
|
140
|
+
*/
|
|
141
|
+
interface ResolvedQuizPluginConfig {
|
|
142
|
+
i18n: Required<QuizI18nConfig>;
|
|
143
|
+
collections: QuizCollectionOverrides;
|
|
144
|
+
admin: Required<QuizAdminConfig>;
|
|
145
|
+
features: {
|
|
146
|
+
certificateTypes: boolean;
|
|
147
|
+
testsArchiveBlock: boolean;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Quiz Plugin for Payload CMS
|
|
153
|
+
*
|
|
154
|
+
* Adds quiz/test functionality with:
|
|
155
|
+
* - Questions collection with multiple choice support
|
|
156
|
+
* - Tests collection with configurable settings
|
|
157
|
+
* - Certificate Types collection for organizing questions
|
|
158
|
+
* - Quiz UI components for the frontend
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* import { quizPlugin } from 'payload-quiz-plugin'
|
|
163
|
+
*
|
|
164
|
+
* export default buildConfig({
|
|
165
|
+
* plugins: [
|
|
166
|
+
* quizPlugin({
|
|
167
|
+
* i18n: {
|
|
168
|
+
* enabled: true,
|
|
169
|
+
* translations: {
|
|
170
|
+
* fr: { tests: { title: 'Tests' } }
|
|
171
|
+
* }
|
|
172
|
+
* }
|
|
173
|
+
* })
|
|
174
|
+
* ]
|
|
175
|
+
* })
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
declare function quizPlugin(pluginConfig?: QuizPluginConfig): Plugin;
|
|
179
|
+
/**
|
|
180
|
+
* Get the resolved plugin configuration
|
|
181
|
+
* Useful for accessing translations and settings in components
|
|
182
|
+
*/
|
|
183
|
+
declare function getQuizPluginConfig(pluginConfig?: QuizPluginConfig): ResolvedQuizPluginConfig;
|
|
184
|
+
|
|
185
|
+
declare function createCertificateTypesCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
|
|
186
|
+
|
|
187
|
+
declare function createQuestionsCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
|
|
188
|
+
|
|
189
|
+
declare function createTestsCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Quiz Plugin Types
|
|
193
|
+
* These types are standalone and don't depend on generated payload-types
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* Rich text data structure (Lexical format)
|
|
197
|
+
*/
|
|
198
|
+
type RichTextData = {
|
|
199
|
+
root: {
|
|
200
|
+
type: string;
|
|
201
|
+
children: unknown[];
|
|
202
|
+
[key: string]: unknown;
|
|
203
|
+
};
|
|
204
|
+
[key: string]: unknown;
|
|
205
|
+
} | null;
|
|
206
|
+
/**
|
|
207
|
+
* Component for rendering rich text content
|
|
208
|
+
* This allows projects to provide their own RichText component
|
|
209
|
+
*/
|
|
210
|
+
type RichTextRenderer = React.ComponentType<{
|
|
211
|
+
data: RichTextData;
|
|
212
|
+
enableGutter?: boolean;
|
|
213
|
+
className?: string;
|
|
214
|
+
}>;
|
|
215
|
+
/**
|
|
216
|
+
* Choice option in a question
|
|
217
|
+
*/
|
|
218
|
+
type QuizChoice = {
|
|
219
|
+
id?: string | null;
|
|
220
|
+
text: string;
|
|
221
|
+
isCorrect?: boolean | null;
|
|
222
|
+
explanation?: RichTextData;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Question structure for the quiz
|
|
226
|
+
*/
|
|
227
|
+
type QuizQuestion = {
|
|
228
|
+
id: number;
|
|
229
|
+
question: string;
|
|
230
|
+
requiredAnswers?: number | null;
|
|
231
|
+
choices?: QuizChoice[] | null;
|
|
232
|
+
explanation?: RichTextData;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Test configuration
|
|
236
|
+
*/
|
|
237
|
+
type QuizTest = {
|
|
238
|
+
id?: number;
|
|
239
|
+
title: string;
|
|
240
|
+
slug?: string | null;
|
|
241
|
+
questionCount?: number | null;
|
|
242
|
+
timeLimit?: number | null;
|
|
243
|
+
passMark?: number | null;
|
|
244
|
+
questionsPerPage?: number | null;
|
|
245
|
+
allowGoBack?: boolean | null;
|
|
246
|
+
requireAllAnswered?: boolean | null;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* User's answer to a question
|
|
250
|
+
*/
|
|
251
|
+
type UserAnswer = {
|
|
252
|
+
questionId: number;
|
|
253
|
+
selectedChoiceIds: string[];
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* Quiz state for the reducer
|
|
257
|
+
*/
|
|
258
|
+
type QuizState = {
|
|
259
|
+
status: 'loading' | 'ready' | 'in_progress' | 'completed';
|
|
260
|
+
questions: QuizQuestion[];
|
|
261
|
+
currentQuestionIndex: number;
|
|
262
|
+
answers: Map<number, string[]>;
|
|
263
|
+
startTime: number | null;
|
|
264
|
+
endTime: number | null;
|
|
265
|
+
timeRemaining: number;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Actions for the quiz reducer
|
|
269
|
+
*/
|
|
270
|
+
type QuizAction = {
|
|
271
|
+
type: 'SET_QUESTIONS';
|
|
272
|
+
questions: QuizQuestion[];
|
|
273
|
+
} | {
|
|
274
|
+
type: 'START_QUIZ';
|
|
275
|
+
timeLimit: number;
|
|
276
|
+
} | {
|
|
277
|
+
type: 'SELECT_ANSWER';
|
|
278
|
+
questionId: number;
|
|
279
|
+
choiceId: string;
|
|
280
|
+
isMultiple: boolean;
|
|
281
|
+
} | {
|
|
282
|
+
type: 'DESELECT_ANSWER';
|
|
283
|
+
questionId: number;
|
|
284
|
+
choiceId: string;
|
|
285
|
+
} | {
|
|
286
|
+
type: 'NEXT_QUESTION';
|
|
287
|
+
} | {
|
|
288
|
+
type: 'PREV_QUESTION';
|
|
289
|
+
} | {
|
|
290
|
+
type: 'GO_TO_QUESTION';
|
|
291
|
+
index: number;
|
|
292
|
+
} | {
|
|
293
|
+
type: 'TICK_TIMER';
|
|
294
|
+
} | {
|
|
295
|
+
type: 'FINISH_QUIZ';
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Result of the quiz
|
|
299
|
+
*/
|
|
300
|
+
type QuizResult = {
|
|
301
|
+
totalQuestions: number;
|
|
302
|
+
correctAnswers: number;
|
|
303
|
+
incorrectAnswers: number;
|
|
304
|
+
unanswered: number;
|
|
305
|
+
score: number;
|
|
306
|
+
passed: boolean;
|
|
307
|
+
passMark: number;
|
|
308
|
+
timeTaken: number;
|
|
309
|
+
questionResults: QuestionResult[];
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* Result for a single question
|
|
313
|
+
*/
|
|
314
|
+
type QuestionResult = {
|
|
315
|
+
questionId: number;
|
|
316
|
+
question: string;
|
|
317
|
+
requiredAnswers: number;
|
|
318
|
+
isCorrect: boolean;
|
|
319
|
+
userAnswers: string[];
|
|
320
|
+
correctAnswers: string[];
|
|
321
|
+
choices: {
|
|
322
|
+
id: string;
|
|
323
|
+
text: string;
|
|
324
|
+
isCorrect: boolean;
|
|
325
|
+
wasSelected: boolean;
|
|
326
|
+
explanation?: RichTextData;
|
|
327
|
+
}[];
|
|
328
|
+
explanation?: RichTextData;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Types for TestCard component
|
|
333
|
+
* These are standalone and don't depend on generated payload-types
|
|
334
|
+
*/
|
|
335
|
+
/**
|
|
336
|
+
* Media object structure (simplified)
|
|
337
|
+
*/
|
|
338
|
+
type TestCardMedia = {
|
|
339
|
+
url?: string | null;
|
|
340
|
+
alt?: string | null;
|
|
341
|
+
width?: number | null;
|
|
342
|
+
height?: number | null;
|
|
343
|
+
[key: string]: unknown;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Certificate type structure
|
|
347
|
+
*/
|
|
348
|
+
type TestCardCertificateType = {
|
|
349
|
+
id?: number | string;
|
|
350
|
+
shortName?: string | null;
|
|
351
|
+
title?: string | null;
|
|
352
|
+
[key: string]: unknown;
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Test meta information
|
|
356
|
+
*/
|
|
357
|
+
type TestCardMeta = {
|
|
358
|
+
title?: string | null;
|
|
359
|
+
description?: string | null;
|
|
360
|
+
image?: TestCardMedia | number | string | null;
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Data structure for a test card
|
|
364
|
+
*/
|
|
365
|
+
type TestCardData = {
|
|
366
|
+
id?: number | string;
|
|
367
|
+
slug?: string | null;
|
|
368
|
+
title?: string | null;
|
|
369
|
+
certificateType?: TestCardCertificateType | number | string | null;
|
|
370
|
+
meta?: TestCardMeta | null;
|
|
371
|
+
questionCount?: number | null;
|
|
372
|
+
timeLimit?: number | null;
|
|
373
|
+
passMark?: number | null;
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Props for the TestCard component
|
|
377
|
+
*/
|
|
378
|
+
type TestCardProps = {
|
|
379
|
+
/** Test data to display */
|
|
380
|
+
doc?: TestCardData;
|
|
381
|
+
/** Override title */
|
|
382
|
+
title?: string;
|
|
383
|
+
/** Additional class names */
|
|
384
|
+
className?: string;
|
|
385
|
+
/** Custom media renderer component */
|
|
386
|
+
MediaComponent?: React.ComponentType<{
|
|
387
|
+
resource: TestCardMedia;
|
|
388
|
+
size?: string;
|
|
389
|
+
fill?: boolean;
|
|
390
|
+
pictureClassName?: string;
|
|
391
|
+
imgClassName?: string;
|
|
392
|
+
}>;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Create a translation function for a specific locale
|
|
397
|
+
*/
|
|
398
|
+
declare function createTranslator(config: ResolvedQuizPluginConfig, locale?: string): (key: string, params?: Record<string, string | number>, fallback?: string) => string;
|
|
399
|
+
/**
|
|
400
|
+
* Get all merged translations for a locale (useful for passing to client components)
|
|
401
|
+
*/
|
|
402
|
+
declare function getTranslations(config: ResolvedQuizPluginConfig, locale?: string): QuizTranslations;
|
|
403
|
+
/**
|
|
404
|
+
* Check if a locale has translations available
|
|
405
|
+
*/
|
|
406
|
+
declare function hasLocale(config: ResolvedQuizPluginConfig, locale: string): boolean;
|
|
407
|
+
/**
|
|
408
|
+
* Get all available locales
|
|
409
|
+
*/
|
|
410
|
+
declare function getAvailableLocales(config: ResolvedQuizPluginConfig): string[];
|
|
411
|
+
|
|
412
|
+
type TestsArchiveBlockConfig = {
|
|
413
|
+
/** Override the block slug */
|
|
414
|
+
slug?: string;
|
|
415
|
+
/** Override labels */
|
|
416
|
+
labels?: {
|
|
417
|
+
singular?: string;
|
|
418
|
+
plural?: string;
|
|
419
|
+
};
|
|
420
|
+
/** Additional fields to add to the block */
|
|
421
|
+
additionalFields?: Block['fields'];
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* Creates the TestsArchiveBlock configuration
|
|
425
|
+
* This block allows displaying a grid of tests filtered by certificate type
|
|
426
|
+
*/
|
|
427
|
+
declare function createTestsArchiveBlock(pluginConfig: ResolvedQuizPluginConfig, blockConfig?: TestsArchiveBlockConfig): Block;
|
|
428
|
+
/**
|
|
429
|
+
* Default TestsArchive block for quick setup
|
|
430
|
+
* Uses default plugin configuration
|
|
431
|
+
*/
|
|
432
|
+
declare const TestsArchiveBlock: Block;
|
|
433
|
+
|
|
434
|
+
export { type QuestionResult, type QuizAction, type QuizAdminConfig, type QuizChoice, type QuizCollectionOverrides, type QuizI18nConfig, type QuizPluginConfig, type QuizQuestion, type QuizResult, type QuizState, type QuizTest, type QuizTranslations, type ResolvedQuizPluginConfig, type RichTextData, type RichTextRenderer, type TestCardCertificateType, type TestCardData, type TestCardMedia, type TestCardMeta, type TestCardProps, TestsArchiveBlock, type TestsArchiveBlockConfig, type UserAnswer, createCertificateTypesCollection, createQuestionsCollection, createTestsArchiveBlock, createTestsCollection, createTranslator, getAvailableLocales, getQuizPluginConfig, getTranslations, hasLocale, quizPlugin };
|