@vue-skuilder/edit-ui 0.1.1

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.
Files changed (28) hide show
  1. package/dist/assets/index.css +1 -0
  2. package/dist/edit-ui.es.js +71090 -0
  3. package/dist/edit-ui.es.js.map +1 -0
  4. package/dist/edit-ui.umd.js +83 -0
  5. package/dist/edit-ui.umd.js.map +1 -0
  6. package/package.json +67 -0
  7. package/src/components/BulkImport/CardPreviewList.vue +345 -0
  8. package/src/components/BulkImportView.vue +633 -0
  9. package/src/components/CourseEditor.vue +164 -0
  10. package/src/components/ViewableDataInputForm/DataInputForm.vue +533 -0
  11. package/src/components/ViewableDataInputForm/FieldInput.types.ts +33 -0
  12. package/src/components/ViewableDataInputForm/FieldInputs/AudioInput.vue +188 -0
  13. package/src/components/ViewableDataInputForm/FieldInputs/ChessPuzzleInput.vue +79 -0
  14. package/src/components/ViewableDataInputForm/FieldInputs/FieldInput.css +12 -0
  15. package/src/components/ViewableDataInputForm/FieldInputs/ImageInput.vue +231 -0
  16. package/src/components/ViewableDataInputForm/FieldInputs/IntegerInput.vue +49 -0
  17. package/src/components/ViewableDataInputForm/FieldInputs/MarkdownInput.vue +34 -0
  18. package/src/components/ViewableDataInputForm/FieldInputs/MediaDragDropUploader.vue +246 -0
  19. package/src/components/ViewableDataInputForm/FieldInputs/MidiInput.vue +113 -0
  20. package/src/components/ViewableDataInputForm/FieldInputs/NumberInput.vue +49 -0
  21. package/src/components/ViewableDataInputForm/FieldInputs/OptionsFieldInput.ts +161 -0
  22. package/src/components/ViewableDataInputForm/FieldInputs/StringInput.vue +49 -0
  23. package/src/components/ViewableDataInputForm/FieldInputs/typeValidators.ts +49 -0
  24. package/src/components/index.ts +21 -0
  25. package/src/index.ts +6 -0
  26. package/src/stores/useDataInputFormStore.ts +49 -0
  27. package/src/stores/useFieldInputStore.ts +191 -0
  28. package/src/vue-shims.d.ts +5 -0
@@ -0,0 +1,633 @@
1
+ <template>
2
+ <v-container fluid>
3
+ <v-row v-if="!parsingComplete">
4
+ <v-col cols="12">
5
+ <v-textarea
6
+ v-model="bulkText"
7
+ label="Bulk Card Input"
8
+ placeholder="Paste card data here.
9
+ Separate cards with two consecutive '---' lines on their own lines.
10
+ Example:
11
+ Card 1 Question
12
+ {{blank}}
13
+ tags: tagA, tagB
14
+ elo: 1500
15
+ ---
16
+ ---
17
+ Card 2 Question
18
+ Another {{blank}}
19
+ elo: 1200
20
+ tags: tagC"
21
+ rows="15"
22
+ variant="outlined"
23
+ data-cy="bulk-import-textarea"
24
+ ></v-textarea>
25
+ </v-col>
26
+ </v-row>
27
+
28
+ <!-- Card Parsing Summary Section -->
29
+ <v-row v-if="parsingComplete" class="mb-4">
30
+ <v-col cols="12" md="4">
31
+ <v-card border>
32
+ <v-card-title>Parsing Summary</v-card-title>
33
+ <v-card-text>
34
+ <p>
35
+ <strong>{{ parsedCards.length }}</strong> card(s) parsed and ready for import.
36
+ </p>
37
+ <div v-if="parsedCards.length > 0">
38
+ <strong>Tags Found:</strong>
39
+ <template v-if="uniqueTags.length > 0">
40
+ <v-chip v-for="tag in uniqueTags" :key="tag" class="mr-1 mb-1" size="small" label>
41
+ {{ tag }}
42
+ </v-chip>
43
+ </template>
44
+ <template v-else>
45
+ <span class="text--secondary">No unique tags identified across parsed cards.</span>
46
+ </template>
47
+ </div>
48
+ </v-card-text>
49
+ </v-card>
50
+ </v-col>
51
+ <v-col cols="12" md="8">
52
+ <card-preview-list
53
+ v-if="parsedCards.length > 0"
54
+ ref="cardPreviewList"
55
+ v-model:parsed-cards="parsedCards"
56
+ :data-shape="dataShape"
57
+ :view-components="cardViewComponents"
58
+ @edit-card="handleEditCard"
59
+ @delete-card="handleDeleteCard"
60
+ />
61
+ </v-col>
62
+ </v-row>
63
+
64
+ <!-- Card Edit Dialog -->
65
+ <v-dialog v-model="showEditDialog" max-width="800px">
66
+ <v-card>
67
+ <v-card-title>Edit Card</v-card-title>
68
+ <v-card-text>
69
+ <v-form @submit.prevent="saveEditedCard" @keydown.esc="closeEditDialog">
70
+ <v-textarea
71
+ ref="markdownTextarea"
72
+ v-model="editedMarkdown"
73
+ label="Card Content"
74
+ rows="6"
75
+ auto-grow
76
+ variant="outlined"
77
+ @keydown.ctrl.enter="saveEditedCard"
78
+ ></v-textarea>
79
+
80
+ <div class="my-4">
81
+ <div class="d-flex align-center mb-2">
82
+ <h3 class="text-subtitle-1 mr-2">Tags</h3>
83
+ <v-text-field
84
+ v-model="newTagText"
85
+ density="compact"
86
+ hide-details
87
+ placeholder="Add a tag"
88
+ variant="outlined"
89
+ class="mr-2"
90
+ @keydown.enter.prevent="addTag"
91
+ @keydown.esc="closeEditDialog"
92
+ ></v-text-field>
93
+ <v-btn size="small" color="primary" variant="text" @click="addTag">Add</v-btn>
94
+ </div>
95
+ <div class="d-flex flex-wrap">
96
+ <v-chip v-for="tag in editedTags" :key="tag" closable class="mr-1 mb-1" @click:close="removeTag(tag)">
97
+ {{ tag }}
98
+ </v-chip>
99
+ </div>
100
+ </div>
101
+
102
+ <v-text-field
103
+ v-model.number="editedElo"
104
+ label="ELO Rating (optional)"
105
+ type="number"
106
+ variant="outlined"
107
+ min="0"
108
+ max="3000"
109
+ ></v-text-field>
110
+ </v-form>
111
+ </v-card-text>
112
+ <v-card-actions>
113
+ <v-spacer></v-spacer>
114
+ <v-btn color="grey" variant="text" @click="closeEditDialog">
115
+ Cancel
116
+ <v-tooltip activator="parent" location="top">Press ESC</v-tooltip>
117
+ </v-btn>
118
+ <v-btn color="primary" @click="saveEditedCard">
119
+ Save
120
+ <v-tooltip activator="parent" location="top">Press Ctrl+Enter</v-tooltip>
121
+ </v-btn>
122
+ </v-card-actions>
123
+ </v-card>
124
+ </v-dialog>
125
+
126
+ <v-row>
127
+ <v-col cols="12">
128
+ <!-- Button for initial parsing -->
129
+ <v-btn
130
+ v-if="!parsingComplete"
131
+ color="primary"
132
+ :loading="processing"
133
+ :disabled="!bulkText.trim() || processing"
134
+ data-cy="bulk-import-parse-btn"
135
+ @click="handleInitialParse"
136
+ >
137
+ Parse Cards
138
+ <v-icon end>mdi-play-circle-outline</v-icon>
139
+ </v-btn>
140
+
141
+ <!-- Buttons for post-parsing stage -->
142
+ <template v-if="parsingComplete">
143
+ <v-btn
144
+ color="primary"
145
+ class="mr-2"
146
+ :loading="processing"
147
+ :disabled="parsedCards.length === 0 || processing || importAttempted"
148
+ data-cy="bulk-import-confirm-btn"
149
+ @click="confirmAndImportCards"
150
+ >
151
+ Confirm and Import {{ parsedCards.length }} Card(s)
152
+ <v-icon end>mdi-check-circle-outline</v-icon>
153
+ </v-btn>
154
+ <v-btn
155
+ v-if="!importAttempted"
156
+ variant="outlined"
157
+ color="grey-darken-1"
158
+ :disabled="processing"
159
+ data-cy="bulk-import-edit-again-btn"
160
+ @click="resetToInputStage"
161
+ >
162
+ <v-icon start>mdi-pencil</v-icon>
163
+ Back to bulk-editor.
164
+ </v-btn>
165
+ <v-btn
166
+ v-if="importAttempted"
167
+ variant="outlined"
168
+ color="blue-darken-1"
169
+ :disabled="processing"
170
+ data-cy="bulk-import-add-another-btn"
171
+ @click="startNewBulkImport"
172
+ >
173
+ <v-icon start>mdi-plus-circle-outline</v-icon>
174
+ Add Another Bulk Import
175
+ </v-btn>
176
+ </template>
177
+ </v-col>
178
+ </v-row>
179
+ <v-row v-if="results.length > 0">
180
+ <v-col cols="12">
181
+ <v-list density="compact">
182
+ <v-list-subheader>Import Results</v-list-subheader>
183
+ <v-list-item
184
+ v-for="(result, index) in results"
185
+ :key="index"
186
+ :class="{
187
+ 'lime-lighten-5': result.status === 'success',
188
+ 'red-lighten-5': result.status === 'error',
189
+ 'force-dark-text': result.status === 'success' || result.status === 'error',
190
+ }"
191
+ >
192
+ <v-list-item-title>
193
+ <v-icon :color="result.status === 'success' ? 'green' : 'red'">
194
+ {{ result.status === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle' }}
195
+ </v-icon>
196
+ Card {{ index + 1 }}
197
+ </v-list-item-title>
198
+ <v-list-item-subtitle>
199
+ <div v-if="result.message" class="ml-6">{{ result.message }}</div>
200
+ <div v-if="result.cardId" class="ml-6">ID: {{ result.cardId }}</div>
201
+ <details v-if="result.status === 'error' && result.originalText" class="ml-6">
202
+ <summary>Original Input</summary>
203
+ <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 5px">{{
204
+ result.originalText
205
+ }}</pre>
206
+ </details>
207
+ </v-list-item-subtitle>
208
+ </v-list-item>
209
+ </v-list>
210
+ </v-col>
211
+ </v-row>
212
+ </v-container>
213
+ </template>
214
+
215
+ <script lang="ts">
216
+ import { defineComponent, PropType } from 'vue';
217
+ import {
218
+ CourseConfig,
219
+ DataShape,
220
+ Status,
221
+ NameSpacer,
222
+ ParsedCard,
223
+ parseBulkTextToCards,
224
+ isValidBulkFormat,
225
+ } from '@vue-skuilder/common';
226
+ import { BlanksCardDataShapes, allCourses } from '@vue-skuilder/courses';
227
+ import { ViewComponent, getCurrentUser, alertUser } from '@vue-skuilder/common-ui';
228
+ import {
229
+ getDataLayer,
230
+ CourseDBInterface,
231
+ ImportResult,
232
+ importParsedCards,
233
+ validateProcessorConfig,
234
+ } from '@vue-skuilder/db';
235
+ import CardPreviewList from './BulkImport/CardPreviewList.vue';
236
+
237
+ export default defineComponent({
238
+ name: 'BulkImportView',
239
+ components: {
240
+ CardPreviewList,
241
+ },
242
+ props: {
243
+ courseCfg: {
244
+ type: Object as PropType<CourseConfig>,
245
+ required: true,
246
+ },
247
+ },
248
+ data() {
249
+ return {
250
+ bulkText: '',
251
+ parsedCards: [] as ParsedCard[],
252
+ parsingComplete: false,
253
+ importAttempted: false,
254
+ results: [] as ImportResult[],
255
+ processing: false, // Will be used for both parsing and import stages
256
+ courseDB: null as CourseDBInterface | null,
257
+ dataShape: BlanksCardDataShapes[0],
258
+ cardViewComponents: [] as ViewComponent[],
259
+ editingCard: null as ParsedCard | null,
260
+ editingCardIndex: -1,
261
+ showEditDialog: false,
262
+ editedMarkdown: '',
263
+ editedTags: [] as string[],
264
+ editedElo: undefined as number | undefined,
265
+ newTagText: '',
266
+ };
267
+ },
268
+ computed: {
269
+ uniqueTags(): string[] {
270
+ if (!this.parsedCards || this.parsedCards.length === 0) {
271
+ return [];
272
+ }
273
+ const allTags = this.parsedCards.reduce((acc, card) => {
274
+ if (card.tags && card.tags.length > 0) {
275
+ acc.push(...card.tags);
276
+ }
277
+ return acc;
278
+ }, [] as string[]);
279
+ return [...new Set(allTags)].sort();
280
+ },
281
+ },
282
+ created() {
283
+ if (this.courseCfg?.courseID) {
284
+ this.courseDB = getDataLayer().getCourseDB(this.courseCfg.courseID);
285
+
286
+ // Validate that we have datashapes in the course config
287
+ if (!this.courseCfg.dataShapes || this.courseCfg.dataShapes.length === 0) {
288
+ console.error('[BulkImportView] Course config does not contain any dataShapes.');
289
+ alertUser({
290
+ text: 'Course configuration has no dataShapes. Bulk import may not work correctly.',
291
+ status: Status.warning,
292
+ });
293
+ } else {
294
+ // Get the data shape and view components for card preview
295
+ this.initializeCardPreviewComponents();
296
+ }
297
+ } else {
298
+ console.error('[BulkImportView] Course config or Course ID is missing.');
299
+ alertUser({
300
+ text: 'Course configuration is missing. Cannot initialize bulk import.',
301
+ status: Status.error,
302
+ });
303
+ }
304
+ },
305
+ methods: {
306
+ handleDeleteCard(index: number) {
307
+ // The card is already removed from the parsedCards array via v-model
308
+ // This method can be used for additional processing if needed
309
+ console.log(`[BulkImportView] Card at index ${index} was deleted`);
310
+
311
+ // Show alert to confirm deletion
312
+ alertUser({
313
+ text: 'Card removed from import list',
314
+ status: Status.ok,
315
+ });
316
+ },
317
+
318
+ handleEditCard(card: ParsedCard, index: number) {
319
+ // Disable keyboard shortcuts while editing
320
+ if (this.$refs.cardPreviewList) {
321
+ (this.$refs.cardPreviewList as { toggleShortcuts: (enabled: boolean) => void }).toggleShortcuts(false);
322
+ }
323
+
324
+ this.editingCard = { ...card }; // Create a copy
325
+ this.editingCardIndex = index;
326
+ this.editedMarkdown = card.markdown;
327
+ this.editedTags = [...card.tags];
328
+ this.editedElo = card.elo;
329
+ this.showEditDialog = true;
330
+
331
+ // Focus the text area after dialog opens
332
+ this.$nextTick(() => {
333
+ if (this.$refs.markdownTextarea) {
334
+ (this.$refs.markdownTextarea as { $el: HTMLElement }).$el.querySelector('textarea')?.focus();
335
+ }
336
+ });
337
+ },
338
+
339
+ saveEditedCard() {
340
+ if (this.editingCardIndex < 0 || !this.editingCard) return;
341
+
342
+ // Create updated card
343
+ const updatedCard: ParsedCard = {
344
+ markdown: this.editedMarkdown,
345
+ tags: this.editedTags,
346
+ elo: this.editedElo,
347
+ };
348
+
349
+ // Update the card in the array
350
+ const updatedCards = [...this.parsedCards];
351
+ updatedCards[this.editingCardIndex] = updatedCard;
352
+ this.parsedCards = updatedCards;
353
+
354
+ // Reset editing state
355
+ this.closeEditDialog();
356
+
357
+ // Show alert to confirm edit
358
+ alertUser({
359
+ text: 'Card updated successfully',
360
+ status: Status.ok,
361
+ });
362
+ },
363
+
364
+ closeEditDialog() {
365
+ this.showEditDialog = false;
366
+ this.editingCard = null;
367
+ this.editingCardIndex = -1;
368
+ this.editedMarkdown = '';
369
+ this.editedTags = [];
370
+ this.editedElo = undefined;
371
+
372
+ // Re-enable keyboard shortcuts after editing
373
+ setTimeout(() => {
374
+ if (this.$refs.cardPreviewList) {
375
+ (this.$refs.cardPreviewList as { toggleShortcuts: (enabled: boolean) => void }).toggleShortcuts(true);
376
+ }
377
+ }, 100);
378
+ },
379
+
380
+ addTag() {
381
+ const newTag = this.newTagText.trim();
382
+ if (newTag && !this.editedTags.includes(newTag)) {
383
+ this.editedTags.push(newTag);
384
+ this.newTagText = '';
385
+ }
386
+ },
387
+
388
+ removeTag(tag: string) {
389
+ this.editedTags = this.editedTags.filter((t) => t !== tag);
390
+ },
391
+
392
+ initializeCardPreviewComponents() {
393
+ // Use the first data shape from the course config
394
+ const configDataShape = this.courseCfg?.dataShapes?.[0];
395
+ if (!configDataShape) return;
396
+
397
+ // Validate that we're using the correct dataShape for consistency with the import process
398
+ if (this.dataShape.name !== BlanksCardDataShapes[0].name) {
399
+ this.dataShape = BlanksCardDataShapes[0];
400
+ }
401
+
402
+ // Get the code course from the dataShape descriptor
403
+ const descriptor = NameSpacer.getDataShapeDescriptor(configDataShape.name);
404
+ const course = allCourses.getCourse(descriptor.course);
405
+
406
+ if (course) {
407
+ // Get view components for this dataShape
408
+ this.cardViewComponents = [];
409
+
410
+ // Add base question type views
411
+ course.getBaseQTypes().forEach((qType) => {
412
+ if (qType.dataShapes[0].name === this.dataShape?.name) {
413
+ this.cardViewComponents = this.cardViewComponents.concat(qType.views);
414
+ }
415
+ });
416
+
417
+ // Add question-specific views
418
+ for (const q of configDataShape.questionTypes) {
419
+ const qDescriptor = NameSpacer.getQuestionDescriptor(q);
420
+ const questionViews = course.getQuestion(qDescriptor.questionType)?.views || [];
421
+ this.cardViewComponents = this.cardViewComponents.concat(questionViews);
422
+ }
423
+
424
+ console.log(`[BulkImportView] Loaded ${this.cardViewComponents.length} view components for card preview`);
425
+ }
426
+ },
427
+
428
+ resetToInputStage() {
429
+ this.parsingComplete = false;
430
+ this.parsedCards = [];
431
+ this.importAttempted = false; // Reset import attempt flag
432
+ // Optionally keep results if you want to show them even after going back
433
+ // this.results = [];
434
+ // this.bulkText = ''; // Optionally clear the bulk text
435
+ },
436
+
437
+ startNewBulkImport() {
438
+ this.bulkText = '';
439
+ this.results = [];
440
+ this.parsedCards = [];
441
+ this.parsingComplete = false;
442
+ this.importAttempted = false;
443
+ },
444
+
445
+ handleInitialParse() {
446
+ if (!this.courseDB) {
447
+ alertUser({
448
+ text: 'Database connection not available. Cannot process cards.',
449
+ status: Status.error,
450
+ });
451
+ return;
452
+ }
453
+
454
+ // isValidBulkFormat calls alertUser internally if format is invalid.
455
+ if (!isValidBulkFormat(this.bulkText)) {
456
+ return;
457
+ }
458
+
459
+ this.processing = true;
460
+ this.results = []; // Clear previous import results
461
+ this.parsedCards = []; // Clear previously parsed cards
462
+ this.parsingComplete = false; // Reset parsing complete state
463
+
464
+ try {
465
+ this.parsedCards = parseBulkTextToCards(this.bulkText);
466
+
467
+ if (this.parsedCards.length === 0) {
468
+ alertUser({
469
+ text: 'No cards could be parsed from the input. Please check the format and ensure cards are separated by two "---" lines and that cards have content.',
470
+ status: Status.warning,
471
+ });
472
+ this.processing = false;
473
+ return;
474
+ }
475
+
476
+ // Initialize card preview components if not already done
477
+ if (this.cardViewComponents.length === 0) {
478
+ this.initializeCardPreviewComponents();
479
+ }
480
+
481
+ // Successfully parsed, ready for review stage
482
+ this.parsingComplete = true;
483
+ console.log(`[BulkImportView] Successfully parsed ${this.parsedCards.length} cards for preview`);
484
+ } catch (error) {
485
+ console.error('[BulkImportView] Error parsing bulk text:', error);
486
+ alertUser({
487
+ text: `Error parsing cards: ${error instanceof Error ? error.message : 'Unknown error'}`,
488
+ status: Status.error,
489
+ });
490
+ } finally {
491
+ this.processing = false;
492
+ }
493
+ },
494
+
495
+ async confirmAndImportCards() {
496
+ if (!this.courseDB) {
497
+ alertUser({ text: 'Database connection lost before import.', status: Status.error });
498
+ this.processing = false; // Ensure processing is false
499
+ return;
500
+ }
501
+ if (this.parsedCards.length === 0) {
502
+ alertUser({ text: 'No parsed cards to import.', status: Status.warning });
503
+ this.processing = false; // Ensure processing is false
504
+ return;
505
+ }
506
+
507
+ // Validate that we have datashapes in the course config
508
+ if (!this.courseCfg?.dataShapes || this.courseCfg.dataShapes.length === 0) {
509
+ alertUser({
510
+ text: 'This course has no data shapes configured. Cannot import cards.',
511
+ status: Status.error,
512
+ });
513
+ this.processing = false;
514
+ return;
515
+ }
516
+
517
+ this.processing = true;
518
+ this.results = []; // Clear results from parsing stage or previous attempts
519
+
520
+ const currentUser = await getCurrentUser();
521
+ const userName = currentUser.getUsername();
522
+
523
+ const dataShapeToUse: DataShape = BlanksCardDataShapes[0];
524
+
525
+ if (!dataShapeToUse) {
526
+ alertUser({ text: 'Critical: Could not find BlanksCardDataShapes. Aborting import.', status: Status.error });
527
+ this.processing = false;
528
+ return;
529
+ }
530
+
531
+ const configDataShape = this.courseCfg?.dataShapes?.[0];
532
+ if (!configDataShape) {
533
+ alertUser({
534
+ text: 'Critical: No data shapes found in course configuration. Aborting import.',
535
+ status: Status.error,
536
+ });
537
+ this.processing = false;
538
+ return;
539
+ }
540
+
541
+ const codeCourse = NameSpacer.getDataShapeDescriptor(configDataShape.name).course;
542
+
543
+ const processorConfig = {
544
+ dataShape: dataShapeToUse,
545
+ courseCode: codeCourse,
546
+ userName: userName,
547
+ };
548
+
549
+ const validation = validateProcessorConfig(processorConfig);
550
+ if (!validation.isValid) {
551
+ alertUser({
552
+ text: validation.errorMessage || 'Invalid processor configuration for import.',
553
+ status: Status.error,
554
+ });
555
+ this.processing = false;
556
+ return;
557
+ }
558
+
559
+ console.log('[BulkImportView] Starting import of parsed cards:', {
560
+ courseID: this.courseCfg.courseID,
561
+ dataShapeToUse: dataShapeToUse.name,
562
+ courseCode: codeCourse,
563
+ numberOfCards: this.parsedCards.length,
564
+ });
565
+
566
+ try {
567
+ this.results = await importParsedCards(this.parsedCards, this.courseDB, processorConfig);
568
+ } catch (error) {
569
+ console.error('[BulkImportView] Error importing parsed cards:', error);
570
+ this.results.push({
571
+ originalText: 'Bulk Operation Error',
572
+ status: 'error',
573
+ message: `Critical error during import: ${error instanceof Error ? error.message : 'Unknown error'}`,
574
+ });
575
+ } finally {
576
+ this.processing = false;
577
+ this.importAttempted = true; // Mark that an import attempt has been made
578
+ }
579
+
580
+ if (this.results.every((r) => r.status === 'success') && this.results.length > 0) {
581
+ // All successful, optionally reset
582
+ // this.bulkText = ''; // Clear input text
583
+ // this.parsingComplete = false; // Go back to input stage
584
+ // this.parsedCards = [];
585
+ alertUser({ text: `${this.results.length} card(s) imported successfully!`, status: Status.ok });
586
+ } else if (this.results.some((r) => r.status === 'error')) {
587
+ alertUser({ text: 'Some cards failed to import. Please review the results below.', status: Status.warning });
588
+ }
589
+ },
590
+ },
591
+ });
592
+ </script>
593
+
594
+ <style scoped>
595
+ .lime-lighten-5 {
596
+ background-color: #f9fbe7 !important; /* Vuetify's lime lighten-5 */
597
+ }
598
+ .red-lighten-5 {
599
+ background-color: #ffebee !important; /* Vuetify's red lighten-5 */
600
+ }
601
+ pre {
602
+ white-space: pre-wrap;
603
+ word-wrap: break-word;
604
+ background-color: #f5f5f5;
605
+ padding: 10px;
606
+ border-radius: 4px;
607
+ margin-top: 5px;
608
+ }
609
+ .force-dark-text {
610
+ color: rgba(0, 0, 0, 0.87) !important;
611
+ }
612
+ /* Ensure child elements also get dark text if not overridden */
613
+ .force-dark-text .v-list-item-subtitle,
614
+ .force-dark-text .v-list-item-title,
615
+ .force-dark-text div, /* Ensure divs within the list item also get dark text */
616
+ .force-dark-text summary {
617
+ /* Ensure summary elements for <details> also get dark text */
618
+ color: rgba(0, 0, 0, 0.87) !important;
619
+ }
620
+ /* Icons are handled by their :color prop, so no specific override needed here unless that changes. */
621
+
622
+ /* Card Preview Styling */
623
+ .card-preview-container {
624
+ border-radius: 4px;
625
+ max-width: 100%;
626
+ }
627
+
628
+ @media (min-width: 960px) {
629
+ .card-preview-container {
630
+ max-width: 65%;
631
+ }
632
+ }
633
+ </style>